1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.app.SearchManager;
20 import android.appwidget.AppWidgetManager;
21 import android.appwidget.AppWidgetProviderInfo;
22 import android.content.*;
23 import android.content.Intent.ShortcutIconResource;
24 import android.content.pm.ActivityInfo;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.pm.ResolveInfo;
29 import android.content.res.Configuration;
30 import android.content.res.Resources;
31 import android.database.Cursor;
32 import android.graphics.Bitmap;
33 import android.graphics.BitmapFactory;
34 import android.net.Uri;
35 import android.os.Environment;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.Parcelable;
39 import android.os.Process;
40 import android.os.RemoteException;
41 import android.os.SystemClock;
42 import android.provider.BaseColumns;
43 import android.text.TextUtils;
44 import android.util.Log;
45 import android.util.Pair;
46
47 import com.android.launcher3.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
48
49 import java.lang.ref.WeakReference;
50 import java.net.URISyntaxException;
51 import java.text.Collator;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.Collection;
55 import java.util.Collections;
56 import java.util.Comparator;
57 import java.util.HashMap;
58 import java.util.HashSet;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Set;
62 import java.util.TreeMap;
63 import java.util.concurrent.atomic.AtomicBoolean;
64
65 /**
66 * Maintains in-memory state of the Launcher. It is expected that there should be only one
67 * LauncherModel object held in a static. Also provide APIs for updating the database state
68 * for the Launcher.
69 */
70 public class LauncherModel extends BroadcastReceiver {
71 static final boolean DEBUG_LOADERS = false;
72 static final String TAG = "Launcher.Model";
73
74 // true = use a "More Apps" folder for non-workspace apps on upgrade
75 // false = strew non-workspace apps across the workspace on upgrade
76 public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false;
77
78 public static final int LOADER_FLAG_NONE = 0;
79 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
80 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
81
82 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
83 private static final long INVALID_SCREEN_ID = -1L;
84
85 private final boolean mAppsCanBeOnRemoveableStorage;
86 private final boolean mOldContentProviderExists;
87
88 private final LauncherAppState mApp;
89 private final Object mLock = new Object();
90 private DeferredHandler mHandler = new DeferredHandler();
91 private LoaderTask mLoaderTask;
92 private boolean mIsLoaderTaskRunning;
93 private volatile boolean mFlushingWorkerThread;
94
95 // Specific runnable types that are run on the main thread deferred handler, this allows us to
96 // clear all queued binding runnables when the Launcher activity is destroyed.
97 private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
98 private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
99
100
101 private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
102 static {
103 sWorkerThread.start();
104 }
105 private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
106
107 // We start off with everything not loaded. After that, we assume that
108 // our monitoring of the package manager provides all updates and we never
109 // need to do a requery. These are only ever touched from the loader thread.
110 private boolean mWorkspaceLoaded;
111 private boolean mAllAppsLoaded;
112
113 // When we are loading pages synchronously, we can't just post the binding of items on the side
114 // pages as this delays the rotation process. Instead, we wait for a callback from the first
115 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
116 // a normal load, we also clear this set of Runnables.
117 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
118
119 private WeakReference<Callbacks> mCallbacks;
120
121 // < only access in worker thread >
122 AllAppsList mBgAllAppsList;
123
124 // The lock that must be acquired before referencing any static bg data structures. Unlike
125 // other locks, this one can generally be held long-term because we never expect any of these
126 // static data structures to be referenced outside of the worker thread except on the first
127 // load after configuration change.
128 static final Object sBgLock = new Object();
129
130 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
131 // LauncherModel to their ids
132 static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
133
134 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
135 // created by LauncherModel that are directly on the home screen (however, no widgets or
136 // shortcuts within folders).
137 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
138
139 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
140 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
141 new ArrayList<LauncherAppWidgetInfo>();
142
143 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
144 static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
145
146 // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database
147 static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>();
148
149 // sBgWorkspaceScreens is the ordered set of workspace screens.
150 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
151
152 // </ only access in worker thread >
153
154 private IconCache mIconCache;
155 private Bitmap mDefaultIcon;
156
157 protected int mPreviousConfigMcc;
158
159 public interface Callbacks {
160 public boolean setLoadOnResume();
161 public int getCurrentWorkspaceScreen();
162 public void startBinding();
163 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
164 boolean forceAnimateIcons);
165 public void bindScreens(ArrayList<Long> orderedScreenIds);
166 public void bindAddScreens(ArrayList<Long> orderedScreenIds);
167 public void bindFolders(HashMap<Long,FolderInfo> folders);
168 public void finishBindingItems(boolean upgradePath);
169 public void bindAppWidget(LauncherAppWidgetInfo info);
170 public void bindAllApplications(ArrayList<AppInfo> apps);
171 public void bindAppsAdded(ArrayList<Long> newScreens,
172 ArrayList<ItemInfo> addNotAnimated,
173 ArrayList<ItemInfo> addAnimated,
174 ArrayList<AppInfo> addedApps);
175 public void bindAppsUpdated(ArrayList<AppInfo> apps);
176 public void bindComponentsRemoved(ArrayList<String> packageNames,
177 ArrayList<AppInfo> appInfos);
178 public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
179 public void bindSearchablesChanged();
180 public boolean isAllAppsButtonRank(int rank);
181 public void onPageBoundSynchronously(int page);
182 public void dumpLogsToLocalData();
183 }
184
185 public interface ItemInfoFilter {
186 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
187 }
188
189 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
190 Context context = app.getContext();
191 ContentResolver contentResolver = context.getContentResolver();
192
193 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
194 mOldContentProviderExists = (contentResolver.acquireContentProviderClient(
195 LauncherSettings.Favorites.OLD_CONTENT_URI) != null);
196 mApp = app;
197 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
198 mIconCache = iconCache;
199
200 final Resources res = context.getResources();
201 Configuration config = res.getConfiguration();
202 mPreviousConfigMcc = config.mcc;
203 }
204
205 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
206 * posted on the main thread handler. */
207 private void runOnMainThread(Runnable r) {
208 runOnMainThread(r, 0);
209 }
210 private void runOnMainThread(Runnable r, int type) {
211 if (sWorkerThread.getThreadId() == Process.myTid()) {
212 // If we are on the worker thread, post onto the main handler
213 mHandler.post(r);
214 } else {
215 r.run();
216 }
217 }
218
219 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
220 * posted on the worker thread handler. */
221 private static void runOnWorkerThread(Runnable r) {
222 if (sWorkerThread.getThreadId() == Process.myTid()) {
223 r.run();
224 } else {
225 // If we are not on the worker thread, then post to the worker handler
226 sWorker.post(r);
227 }
228 }
229
230 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
231 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
232 }
233
234 static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> items, int[] xy,
235 long screen) {
236 LauncherAppState app = LauncherAppState.getInstance();
237 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
238 final int xCount = (int) grid.numColumns;
239 final int yCount = (int) grid.numRows;
240 boolean[][] occupied = new boolean[xCount][yCount];
241
242 int cellX, cellY, spanX, spanY;
243 for (int i = 0; i < items.size(); ++i) {
244 final ItemInfo item = items.get(i);
245 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
246 if (item.screenId == screen) {
247 cellX = item.cellX;
248 cellY = item.cellY;
249 spanX = item.spanX;
250 spanY = item.spanY;
251 for (int x = cellX; 0 <= x && x < cellX + spanX && x < xCount; x++) {
252 for (int y = cellY; 0 <= y && y < cellY + spanY && y < yCount; y++) {
253 occupied[x][y] = true;
254 }
255 }
256 }
257 }
258 }
259
260 return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied);
261 }
262 static Pair<Long, int[]> findNextAvailableIconSpace(Context context, String name,
263 Intent launchIntent,
264 int firstScreenIndex,
265 ArrayList<Long> workspaceScreens) {
266 // Lock on the app so that we don't try and get the items while apps are being added
267 LauncherAppState app = LauncherAppState.getInstance();
268 LauncherModel model = app.getModel();
269 boolean found = false;
270 synchronized (app) {
271 if (sWorkerThread.getThreadId() != Process.myTid()) {
272 // Flush the LauncherModel worker thread, so that if we just did another
273 // processInstallShortcut, we give it time for its shortcut to get added to the
274 // database (getItemsInLocalCoordinates reads the database)
275 model.flushWorkerThread();
276 }
277 final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
278
279 // Try adding to the workspace screens incrementally, starting at the default or center
280 // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
281 firstScreenIndex = Math.min(firstScreenIndex, workspaceScreens.size());
282 int count = workspaceScreens.size();
283 for (int screen = firstScreenIndex; screen < count && !found; screen++) {
284 int[] tmpCoordinates = new int[2];
285 if (findNextAvailableIconSpaceInScreen(items, tmpCoordinates,
286 workspaceScreens.get(screen))) {
287 // Update the Launcher db
288 return new Pair<Long, int[]>(workspaceScreens.get(screen), tmpCoordinates);
289 }
290 }
291 }
292 return null;
293 }
294
295 <<<<<<< GitAnalyzerPlus_ours
296 public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
297 final ArrayList<AppInfo> allAppsApps) {
298 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
299 addAndBindAddedApps(context, workspaceApps, cb, allAppsApps);
300 ||||||| GitAnalyzerPlus_base
301 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
302 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
303
304 if (allAppsApps == null) {
305 throw new RuntimeException("allAppsApps must not be null");
306 }
307 if (allAppsApps.isEmpty()) {
308 return;
309 }
310
311 // Process the newly added applications and add them to the database first
312 Runnable r = new Runnable() {
313 public void run() {
314 runOnMainThread(new Runnable() {
315 public void run() {
316 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
317 if (callbacks == cb && cb != null) {
318 callbacks.bindAppsAdded(null, null, null, allAppsApps);
319 }
320 }
321 });
322 }
323 };
324 runOnWorkerThread(r);
325 }
326
327 public void addAndBindAddedWorkspaceApps(final Context context,
328 final ArrayList<ItemInfo> workspaceApps) {
329 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
330
331 if (workspaceApps == null) {
332 throw new RuntimeException("workspaceApps and allAppsApps must not be null");
333 }
334 if (workspaceApps.isEmpty()) {
335 return;
336 }
337 =======
338 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
339 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
340
341 if (allAppsApps == null) {
342 throw new RuntimeException("allAppsApps must not be null");
343 }
344 if (allAppsApps.isEmpty()) {
345 return;
346 }
347
348 final ArrayList<AppInfo> restoredAppsFinal = new ArrayList<AppInfo>();
349 Iterator<AppInfo> iter = allAppsApps.iterator();
350 while (iter.hasNext()) {
351 ItemInfo a = iter.next();
352 if (LauncherModel.appWasRestored(ctx, a.getIntent())) {
353 restoredAppsFinal.add((AppInfo) a);
354 }
355 }
356
357 // Process the newly added applications and add them to the database first
358 Runnable r = new Runnable() {
359 public void run() {
360 runOnMainThread(new Runnable() {
361 public void run() {
362 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
363 if (callbacks == cb && cb != null) {
364 callbacks.bindAppsAdded(null, null, null, allAppsApps);
365 if (!restoredAppsFinal.isEmpty()) {
366 callbacks.bindAppsUpdated(restoredAppsFinal);
367 }
368 }
369 }
370 });
371 }
372 };
373 runOnWorkerThread(r);
374 >>>>>>> GitAnalyzerPlus_theirs
375 }
376 public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
377 final Callbacks callbacks, final ArrayList<AppInfo> allAppsApps) {
378 if (workspaceApps == null || allAppsApps == null) {
379 throw new RuntimeException("workspaceApps and allAppsApps must not be null");
380 }
381 if (workspaceApps.isEmpty() && allAppsApps.isEmpty()) {
382 return;
383 }
384 // Process the newly added applications and add them to the database first
385 Runnable r = new Runnable() {
386 public void run() {
387 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
388 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
389 final ArrayList<AppInfo> restoredAppsFinal = new ArrayList<AppInfo>();
390
391 // Get the list of workspace screens. We need to append to this list and
392 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
393 // called.
394 ArrayList<Long> workspaceScreens = new ArrayList<Long>();
395 TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(context);
396 for (Integer i : orderedScreens.keySet()) {
397 long screenId = orderedScreens.get(i);
398 workspaceScreens.add(screenId);
399 }
400
401 synchronized(sBgLock) {
402 Iterator<ItemInfo> iter = workspaceApps.iterator();
403 while (iter.hasNext()) {
404 ItemInfo a = iter.next();
405 final String name = a.title.toString();
406 final Intent launchIntent = a.getIntent();
407
408 // Short-circuit this logic if the icon exists somewhere on the workspace
409 if (LauncherModel.shortcutExists(context, name, launchIntent)) {
410 // Only InstallShortcutReceiver sends us shortcutInfos, ignore them
411 if (a instanceof AppInfo &&
412 LauncherModel.appWasRestored(context, launchIntent)) {
413 restoredAppsFinal.add((AppInfo) a);
414 }
415 continue;
416 }
417
418 // Add this icon to the db, creating a new page if necessary. If there
419 // is only the empty page then we just add items to the first page.
420 // Otherwise, we add them to the next pages.
421 int startSearchPageIndex = workspaceScreens.isEmpty() ? 0 : 1;
422 Pair<Long, int[]> coords = LauncherModel.findNextAvailableIconSpace(context,
423 name, launchIntent, startSearchPageIndex, workspaceScreens);
424 if (coords == null) {
425 LauncherProvider lp = LauncherAppState.getLauncherProvider();
426
427 // If we can't find a valid position, then just add a new screen.
428 // This takes time so we need to re-queue the add until the new
429 // page is added. Create as many screens as necessary to satisfy
430 // the startSearchPageIndex.
431 int numPagesToAdd = Math.max(1, startSearchPageIndex + 1 -
432 workspaceScreens.size());
433 while (numPagesToAdd > 0) {
434 long screenId = lp.generateNewScreenId();
435 // Save the screen id for binding in the workspace
436 workspaceScreens.add(screenId);
437 addedWorkspaceScreensFinal.add(screenId);
438 numPagesToAdd--;
439 }
440
441 // Find the coordinate again
442 coords = LauncherModel.findNextAvailableIconSpace(context,
443 name, launchIntent, startSearchPageIndex, workspaceScreens);
444 }
445 if (coords == null) {
446 throw new RuntimeException("Coordinates should not be null");
447 }
448
449 ShortcutInfo shortcutInfo;
450 if (a instanceof ShortcutInfo) {
451 shortcutInfo = (ShortcutInfo) a;
452 } else if (a instanceof AppInfo) {
453 shortcutInfo = ((AppInfo) a).makeShortcut();
454 } else {
455 throw new RuntimeException("Unexpected info type");
456 }
457
458 // Add the shortcut to the db
459 addItemToDatabase(context, shortcutInfo,
460 LauncherSettings.Favorites.CONTAINER_DESKTOP,
461 coords.first, coords.second[0], coords.second[1], false);
462 // Save the ShortcutInfo for binding in the workspace
463 addedShortcutsFinal.add(shortcutInfo);
464 }
465 }
466
467 // Update the workspace screens
468 updateWorkspaceScreenOrder(context, workspaceScreens);
469
470 if (!addedShortcutsFinal.isEmpty() || !allAppsApps.isEmpty()) {
471 runOnMainThread(new Runnable() {
472 public void run() {
473 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
474 if (callbacks == cb && cb != null) {
475 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
476 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
477 if (!addedShortcutsFinal.isEmpty()) {
478 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
479 long lastScreenId = info.screenId;
480 for (ItemInfo i : addedShortcutsFinal) {
481 if (i.screenId == lastScreenId) {
482 addAnimated.add(i);
483 } else {
484 addNotAnimated.add(i);
485 }
486 }
487 }
488 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
489 <<<<<<< GitAnalyzerPlus_ours
490 addNotAnimated, addAnimated, allAppsApps);
491 ||||||| GitAnalyzerPlus_base
492
493 // Update the workspace screens
494 updateWorkspaceScreenOrder(context, workspaceScreens);
495
496 if (!addedShortcutsFinal.isEmpty()) {
497 runOnMainThread(new Runnable() {
498 public void run() {
499 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
500 if (callbacks == cb && cb != null) {
501 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
502 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
503 if (!addedShortcutsFinal.isEmpty()) {
504 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
505 long lastScreenId = info.screenId;
506 for (ItemInfo i : addedShortcutsFinal) {
507 if (i.screenId == lastScreenId) {
508 addAnimated.add(i);
509 } else {
510 addNotAnimated.add(i);
511 }
512 }
513 }
514 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
515 addNotAnimated, addAnimated, null);
516 }
517 }
518 });
519 }
520 }
521 };
522 runOnWorkerThread(r);
523 }
524
525 public Bitmap getFallbackIcon() {
526 if (mDefaultIcon == null) {
527 final Context context = LauncherAppState.getInstance().getContext();
528 mDefaultIcon = Utilities.createIconBitmap(
529 mIconCache.getFullResDefaultActivityIcon(), context);
530 }
531 return Bitmap.createBitmap(mDefaultIcon);
532 }
533
534 public void unbindItemInfosAndClearQueuedBindRunnables() {
535 if (sWorkerThread.getThreadId() == Process.myTid()) {
536 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
537 =======
538 addNotAnimated, addAnimated, null);
539 if (!restoredAppsFinal.isEmpty()) {
540 callbacks.bindAppsUpdated(restoredAppsFinal);
541 }
542 >>>>>>> GitAnalyzerPlus_theirs
543 }
544 }
545 });
546 }
547 }
548 };
549 runOnWorkerThread(r);
550 }
551
552 public Bitmap getFallbackIcon() {
553 if (mDefaultIcon == null) {
554 final Context context = LauncherAppState.getInstance().getContext();
555 mDefaultIcon = Utilities.createIconBitmap(
556 mIconCache.getFullResDefaultActivityIcon(), context);
557 }
558 return Bitmap.createBitmap(mDefaultIcon);
559 }
560
561 public void unbindItemInfosAndClearQueuedBindRunnables() {
562 if (sWorkerThread.getThreadId() == Process.myTid()) {
563 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
564 "main thread");
565 }
566
567 // Clear any deferred bind runnables
568 mDeferredBindRunnables.clear();
569 // Remove any queued bind runnables
570 mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
571 // Unbind all the workspace items
572 unbindWorkspaceItemsOnMainThread();
573 }
574
575 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
576 void unbindWorkspaceItemsOnMainThread() {
577 // Ensure that we don't use the same workspace items data structure on the main thread
578 // by making a copy of workspace items first.
579 final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
580 final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>();
581 synchronized (sBgLock) {
582 tmpWorkspaceItems.addAll(sBgWorkspaceItems);
583 tmpAppWidgets.addAll(sBgAppWidgets);
584 }
585 Runnable r = new Runnable() {
586 @Override
587 public void run() {
588 for (ItemInfo item : tmpWorkspaceItems) {
589 item.unbind();
590 }
591 for (ItemInfo item : tmpAppWidgets) {
592 item.unbind();
593 }
594 }
595 };
596 runOnMainThread(r);
597 }
598
599 /**
600 * Adds an item to the DB if it was not created previously, or move it to a new
601 * <container, screen, cellX, cellY>
602 */
603 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
604 long screenId, int cellX, int cellY) {
605 if (item.container == ItemInfo.NO_ID) {
606 // From all apps
607 addItemToDatabase(context, item, container, screenId, cellX, cellY, false);
608 } else {
609 // From somewhere else
610 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
611 }
612 }
613
614 static void checkItemInfoLocked(
615 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
616 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
617 if (modelItem != null && item != modelItem) {
618 // check all the data is consistent
619 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
620 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
621 ShortcutInfo shortcut = (ShortcutInfo) item;
622 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
623 modelShortcut.intent.filterEquals(shortcut.intent) &&
624 modelShortcut.id == shortcut.id &&
625 modelShortcut.itemType == shortcut.itemType &&
626 modelShortcut.container == shortcut.container &&
627 modelShortcut.screenId == shortcut.screenId &&
628 modelShortcut.cellX == shortcut.cellX &&
629 modelShortcut.cellY == shortcut.cellY &&
630 modelShortcut.spanX == shortcut.spanX &&
631 modelShortcut.spanY == shortcut.spanY &&
632 ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
633 (modelShortcut.dropPos != null &&
634 shortcut.dropPos != null &&
635 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
636 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
637 // For all intents and purposes, this is the same object
638 return;
639 }
640 }
641
642 // the modelItem needs to match up perfectly with item if our model is
643 // to be consistent with the database-- for now, just require
644 // modelItem == item or the equality check above
645 String msg = "item: " + ((item != null) ? item.toString() : "null") +
646 "modelItem: " +
647 ((modelItem != null) ? modelItem.toString() : "null") +
648 "Error: ItemInfo passed to checkItemInfo doesn't match original";
649 RuntimeException e = new RuntimeException(msg);
650 if (stackTrace != null) {
651 e.setStackTrace(stackTrace);
652 }
653 throw e;
654 }
655 }
656
657 static void checkItemInfo(final ItemInfo item) {
658 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
659 final long itemId = item.id;
660 Runnable r = new Runnable() {
661 public void run() {
662 synchronized (sBgLock) {
663 checkItemInfoLocked(itemId, item, stackTrace);
664 }
665 }
666 };
667 runOnWorkerThread(r);
668 }
669
670 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
671 final ItemInfo item, final String callingFunction) {
672 final long itemId = item.id;
673 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
674 final ContentResolver cr = context.getContentResolver();
675
676 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
677 Runnable r = new Runnable() {
678 public void run() {
679 cr.update(uri, values, null, null);
680 updateItemArrays(item, itemId, stackTrace);
681 }
682 };
683 runOnWorkerThread(r);
684 }
685
686 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
687 final ArrayList<ItemInfo> items, final String callingFunction) {
688 final ContentResolver cr = context.getContentResolver();
689
690 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
691 Runnable r = new Runnable() {
692 public void run() {
693 ArrayList<ContentProviderOperation> ops =
694 new ArrayList<ContentProviderOperation>();
695 int count = items.size();
696 for (int i = 0; i < count; i++) {
697 ItemInfo item = items.get(i);
698 final long itemId = item.id;
699 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
700 ContentValues values = valuesList.get(i);
701
702 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
703 updateItemArrays(item, itemId, stackTrace);
704
705 }
706 try {
707 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
708 } catch (Exception e) {
709 e.printStackTrace();
710 }
711 }
712 };
713 runOnWorkerThread(r);
714 }
715
716 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
717 // Lock on mBgLock *after* the db operation
718 synchronized (sBgLock) {
719 checkItemInfoLocked(itemId, item, stackTrace);
720
721 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
722 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
723 // Item is in a folder, make sure this folder exists
724 if (!sBgFolders.containsKey(item.container)) {
725 // An items container is being set to a that of an item which is not in
726 // the list of Folders.
727 String msg = "item: " + item + " container being set to: " +
728 item.container + ", not in the list of folders";
729 Log.e(TAG, msg);
730 }
731 }
732
733 // Items are added/removed from the corresponding FolderInfo elsewhere, such
734 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
735 // that are on the desktop, as appropriate
736 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
737 if (modelItem != null &&
738 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
739 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
740 switch (modelItem.itemType) {
741 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
742 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
743 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
744 if (!sBgWorkspaceItems.contains(modelItem)) {
745 sBgWorkspaceItems.add(modelItem);
746 }
747 break;
748 default:
749 break;
750 }
751 } else {
752 sBgWorkspaceItems.remove(modelItem);
753 }
754 }
755 }
756
757 public void flushWorkerThread() {
758 mFlushingWorkerThread = true;
759 Runnable waiter = new Runnable() {
760 public void run() {
761 synchronized (this) {
762 notifyAll();
763 mFlushingWorkerThread = false;
764 }
765 }
766 };
767
768 synchronized(waiter) {
769 runOnWorkerThread(waiter);
770 if (mLoaderTask != null) {
771 synchronized(mLoaderTask) {
772 mLoaderTask.notify();
773 }
774 }
775 boolean success = false;
776 while (!success) {
777 try {
778 waiter.wait();
779 success = true;
780 } catch (InterruptedException e) {
781 }
782 }
783 }
784 }
785
786 /**
787 * Move an item in the DB to a new <container, screen, cellX, cellY>
788 */
789 static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
790 final long screenId, final int cellX, final int cellY) {
791 item.container = container;
792 item.cellX = cellX;
793 item.cellY = cellY;
794
795 // We store hotseat items in canonical form which is this orientation invariant position
796 // in the hotseat
797 if (context instanceof Launcher && screenId < 0 &&
798 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
799 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
800 } else {
801 item.screenId = screenId;
802 }
803
804 final ContentValues values = new ContentValues();
805 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
806 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
807 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
808 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
809
810 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
811 }
812
813 /**
814 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
815 * cellX, cellY have already been updated on the ItemInfos.
816 */
817 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
818 final long container, final int screen) {
819
820 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
821 int count = items.size();
822
823 for (int i = 0; i < count; i++) {
824 ItemInfo item = items.get(i);
825 item.container = container;
826
827 // We store hotseat items in canonical form which is this orientation invariant position
828 // in the hotseat
829 if (context instanceof Launcher && screen < 0 &&
830 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
831 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
832 item.cellY);
833 } else {
834 item.screenId = screen;
835 }
836
837 final ContentValues values = new ContentValues();
838 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
839 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
840 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
841 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
842
843 contentValues.add(values);
844 }
845 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
846 }
847
848 /**
849 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
850 */
851 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
852 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
853 item.container = container;
854 item.cellX = cellX;
855 item.cellY = cellY;
856 item.spanX = spanX;
857 item.spanY = spanY;
858
859 // We store hotseat items in canonical form which is this orientation invariant position
860 // in the hotseat
861 if (context instanceof Launcher && screenId < 0 &&
862 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
863 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
864 } else {
865 item.screenId = screenId;
866 }
867
868 final ContentValues values = new ContentValues();
869 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
870 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
871 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
872 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
873 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
874 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
875
876 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
877 }
878
879 /**
880 * Update an item to the database in a specified container.
881 */
882 static void updateItemInDatabase(Context context, final ItemInfo item) {
883 final ContentValues values = new ContentValues();
884 item.onAddToDatabase(values);
885 item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
886 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
887 }
888
889 /**
890 * Returns true if the shortcuts already exists in the database.
891 * we identify a shortcut by its title and intent.
892 */
893 static boolean shortcutExists(Context context, String title, Intent intent) {
894 final ContentResolver cr = context.getContentResolver();
895 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
896 new String[] { "title", "intent" }, "title=? and intent=?",
897 new String[] { title, intent.toUri(0) }, null);
898 boolean result = false;
899 try {
900 result = c.moveToFirst();
901 } finally {
902 c.close();
903 }
904 return result;
905 }
906
907 /**
908 * Returns true if the shortcuts already exists in the database.
909 * we identify a shortcut by the component name of the intent.
910 */
911 static boolean appWasRestored(Context context, Intent intent) {
912 final ContentResolver cr = context.getContentResolver();
913 final ComponentName component = intent.getComponent();
914 if (component == null) {
915 return false;
916 }
917 String componentName = component.flattenToString();
918 final String where = "intent glob \"*component=" + componentName + "*\" and restored = 1";
919 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
920 new String[]{"intent", "restored"}, where, null, null);
921 boolean result = false;
922 try {
923 result = c.moveToFirst();
924 } finally {
925 c.close();
926 }
927 Log.d(TAG, "shortcutWasRestored is " + result + " for " + componentName);
928 return result;
929 }
930
931 /**
932 * Returns an ItemInfo array containing all the items in the LauncherModel.
933 * The ItemInfo.id is not set through this function.
934 */
935 static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) {
936 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
937 final ContentResolver cr = context.getContentResolver();
938 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] {
939 LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER,
940 LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Fav🔵
941 LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null);
942
943 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
944 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
945 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
946 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
947 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
948 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
949 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
950
951 try {
952 while (c.moveToNext()) {
953 ItemInfo item = new ItemInfo();
954 item.cellX = c.getInt(cellXIndex);
955 item.cellY = c.getInt(cellYIndex);
956 item.spanX = Math.max(1, c.getInt(spanXIndex));
957 item.spanY = Math.max(1, c.getInt(spanYIndex));
958 item.container = c.getInt(containerIndex);
959 item.itemType = c.getInt(itemTypeIndex);
960 item.screenId = c.getInt(screenIndex);
961
962 items.add(item);
963 }
964 } catch (Exception e) {
965 items.clear();
966 } finally {
967 c.close();
968 }
969
970 return items;
971 }
972
973 /**
974 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
975 */
976 FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
977 final ContentResolver cr = context.getContentResolver();
978 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
979 "_id=? and (itemType=? or itemType=?)",
980 new String[] { String.valueOf(id),
981 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
982
983 try {
984 if (c.moveToFirst()) {
985 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
986 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
987 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
988 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
989 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
990 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
991
992 FolderInfo folderInfo = null;
993 switch (c.getInt(itemTypeIndex)) {
994 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
995 folderInfo = findOrMakeFolder(folderList, id);
996 break;
997 }
998
999 folderInfo.title = c.getString(titleIndex);
1000 folderInfo.id = id;
1001 folderInfo.container = c.getInt(containerIndex);
1002 folderInfo.screenId = c.getInt(screenIndex);
1003 folderInfo.cellX = c.getInt(cellXIndex);
1004 folderInfo.cellY = c.getInt(cellYIndex);
1005
1006 return folderInfo;
1007 }
1008 } finally {
1009 c.close();
1010 }
1011
1012 return null;
1013 }
1014
1015 /**
1016 * Add an item to the database in a specified container. Sets the container, screen, cellX and
1017 * cellY fields of the item. Also assigns an ID to the item.
1018 */
1019 static void addItemToDatabase(Context context, final ItemInfo item, final long container,
1020 final long screenId, final int cellX, final int cellY, final boolean notify) {
1021 item.container = container;
1022 item.cellX = cellX;
1023 item.cellY = cellY;
1024 // We store hotseat items in canonical form which is this orientation invariant position
1025 // in the hotseat
1026 if (context instanceof Launcher && screenId < 0 &&
1027 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1028 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
1029 } else {
1030 item.screenId = screenId;
1031 }
1032
1033 final ContentValues values = new ContentValues();
1034 final ContentResolver cr = context.getContentResolver();
1035 item.onAddToDatabase(values);
1036
1037 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
1038 values.put(LauncherSettings.Favorites._ID, item.id);
1039 item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
1040
1041 Runnable r = new Runnable() {
1042 public void run() {
1043 cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
1044 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
1045
1046 // Lock on mBgLock *after* the db operation
1047 synchronized (sBgLock) {
1048 checkItemInfoLocked(item.id, item, null);
1049 sBgItemsIdMap.put(item.id, item);
1050 switch (item.itemType) {
1051 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1052 sBgFolders.put(item.id, (FolderInfo) item);
1053 // Fall through
1054 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1055 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1056 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
1057 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1058 sBgWorkspaceItems.add(item);
1059 } else {
1060 if (!sBgFolders.containsKey(item.container)) {
1061 // Adding an item to a folder that doesn't exist.
1062 String msg = "adding item: " + item + " to a folder that " +
1063 " doesn't exist";
1064 Log.e(TAG, msg);
1065 }
1066 }
1067 break;
1068 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1069 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
1070 break;
1071 }
1072 }
1073 }
1074 };
1075 runOnWorkerThread(r);
1076 }
1077
1078 /**
1079 * Creates a new unique child id, for a given cell span across all layouts.
1080 */
1081 static int getCellLayoutChildId(
1082 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
1083 return (((int) container & 0xFF) << 24)
1084 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1085 }
1086
1087 /**
1088 * Removes the specified item from the database
1089 * @param context
1090 * @param item
1091 */
1092 static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1093 final ContentResolver cr = context.getContentResolver();
1094 final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false);
1095
1096 Runnable r = new Runnable() {
1097 public void run() {
1098 cr.delete(uriToDelete, null, null);
1099
1100 // Lock on mBgLock *after* the db operation
1101 synchronized (sBgLock) {
1102 switch (item.itemType) {
1103 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1104 sBgFolders.remove(item.id);
1105 for (ItemInfo info: sBgItemsIdMap.values()) {
1106 if (info.container == item.id) {
1107 // We are deleting a folder which still contains items that
1108 // think they are contained by that folder.
1109 String msg = "deleting a folder (" + item + ") which still " +
1110 "contains items (" + info + ")";
1111 Log.e(TAG, msg);
1112 }
1113 }
1114 sBgWorkspaceItems.remove(item);
1115 break;
1116 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1117 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1118 sBgWorkspaceItems.remove(item);
1119 break;
1120 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1121 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
1122 break;
1123 }
1124 sBgItemsIdMap.remove(item.id);
1125 sBgDbIconCache.remove(item);
1126 }
1127 }
1128 };
1129 runOnWorkerThread(r);
1130 }
1131
1132 /**
1133 * Update the order of the workspace screens in the database. The array list contains
1134 * a list of screen ids in the order that they should appear.
1135 */
1136 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1137 // Log to disk
1138 Launcher.addDumpLog(TAG, "11683562 - updateWorkspaceScreenOrder()", true);
1139 Launcher.addDumpLog(TAG, "11683562 - screens: " + TextUtils.join(", ", screens), true);
1140
1141 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1142 final ContentResolver cr = context.getContentResolver();
1143 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1144
1145 // Remove any negative screen ids -- these aren't persisted
1146 Iterator<Long> iter = screensCopy.iterator();
1147 while (iter.hasNext()) {
1148 long id = iter.next();
1149 if (id < 0) {
1150 iter.remove();
1151 }
1152 }
1153
1154 Runnable r = new Runnable() {
1155 @Override
1156 public void run() {
1157 // Clear the table
1158 cr.delete(uri, null, null);
1159 int count = screensCopy.size();
1160 ContentValues[] values = new ContentValues[count];
1161 for (int i = 0; i < count; i++) {
1162 ContentValues v = new ContentValues();
1163 long screenId = screensCopy.get(i);
1164 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1165 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1166 values[i] = v;
1167 }
1168 cr.bulkInsert(uri, values);
1169
1170 synchronized (sBgLock) {
1171 sBgWorkspaceScreens.clear();
1172 sBgWorkspaceScreens.addAll(screensCopy);
1173 }
1174 }
1175 };
1176 runOnWorkerThread(r);
1177 }
1178
1179 /**
1180 * Remove the contents of the specified folder from the database
1181 */
1182 static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1183 final ContentResolver cr = context.getContentResolver();
1184
1185 Runnable r = new Runnable() {
1186 public void run() {
1187 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
1188 // Lock on mBgLock *after* the db operation
1189 synchronized (sBgLock) {
1190 sBgItemsIdMap.remove(info.id);
1191 sBgFolders.remove(info.id);
1192 sBgDbIconCache.remove(info);
1193 sBgWorkspaceItems.remove(info);
1194 }
1195
1196 cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
1197 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1198 // Lock on mBgLock *after* the db operation
1199 synchronized (sBgLock) {
1200 for (ItemInfo childInfo : info.contents) {
1201 sBgItemsIdMap.remove(childInfo.id);
1202 sBgDbIconCache.remove(childInfo);
1203 }
1204 }
1205 }
1206 };
1207 runOnWorkerThread(r);
1208 }
1209
1210 /**
1211 * Set this as the current Launcher activity object for the loader.
1212 */
1213 public void initialize(Callbacks callbacks) {
1214 synchronized (mLock) {
1215 mCallbacks = new WeakReference<Callbacks>(callbacks);
1216 }
1217 }
1218
1219 /**
1220 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1221 * ACTION_PACKAGE_CHANGED.
1222 */
1223 @Override
1224 public void onReceive(Context context, Intent intent) {
1225 if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
1226
1227 final String action = intent.getAction();
1228
1229 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
1230 || Intent.ACTION_PACKAGE_REMOVED.equals(action)
1231 || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
1232 final String packageName = intent.getData().getSchemeSpecificPart();
1233 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1234
1235 int op = PackageUpdatedTask.OP_NONE;
1236
1237 if (packageName == null || packageName.length() == 0) {
1238 // they sent us a bad intent
1239 return;
1240 }
1241
1242 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
1243 op = PackageUpdatedTask.OP_UPDATE;
1244 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
1245 if (!replacing) {
1246 op = PackageUpdatedTask.OP_REMOVE;
1247 }
1248 // else, we are replacing the package, so a PACKAGE_ADDED will be sent
1249 // later, we will update the package at this time
1250 } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
1251 if (!replacing) {
1252 op = PackageUpdatedTask.OP_ADD;
1253 } else {
1254 op = PackageUpdatedTask.OP_UPDATE;
1255 }
1256 }
1257
1258 if (op != PackageUpdatedTask.OP_NONE) {
1259 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName }));
1260 }
1261
1262 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
1263 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1264 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1265 if (!replacing) {
1266 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
1267 if (mAppsCanBeOnRemoveableStorage) {
1268 // Only rebind if we support removable storage. It catches the case where
1269 // apps on the external sd card need to be reloaded
1270 startLoaderFromBackground();
1271 }
1272 } else {
1273 // If we are replacing then just update the packages in the list
1274 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1275 packages));
1276 }
1277 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
1278 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1279 if (!replacing) {
1280 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1281 enqueuePackageUpdated(new PackageUpdatedTask(
1282 PackageUpdatedTask.OP_UNAVAILABLE, packages));
1283 }
1284 // else, we are replacing the packages, so ignore this event and wait for
1285 // EXTERNAL_APPLICATIONS_AVAILABLE to update the packages at that time
1286 } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1287 // If we have changed locale we need to clear out the labels in all apps/workspace.
1288 forceReload();
1289 } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
1290 // Check if configuration change was an mcc/mnc change which would affect app resources
1291 // and we would need to clear out the labels in all apps/workspace. Same handling as
1292 // above for ACTION_LOCALE_CHANGED
1293 Configuration currentConfig = context.getResources().getConfiguration();
1294 if (mPreviousConfigMcc != currentConfig.mcc) {
1295 Log.d(TAG, "Reload apps on config change. curr_mcc:"
1296 + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc);
1297 forceReload();
1298 }
1299 // Update previousConfig
1300 mPreviousConfigMcc = currentConfig.mcc;
1301 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
1302 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
1303 if (mCallbacks != null) {
1304 Callbacks callbacks = mCallbacks.get();
1305 if (callbacks != null) {
1306 callbacks.bindSearchablesChanged();
1307 }
1308 }
1309 }
1310 }
1311
1312 private void forceReload() {
1313 resetLoadedState(true, true);
1314
1315 // Do this here because if the launcher activity is running it will be restarted.
1316 // If it's not running startLoaderFromBackground will merely tell it that it needs
1317 // to reload.
1318 startLoaderFromBackground();
1319 }
1320
1321 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1322 synchronized (mLock) {
1323 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1324 // mWorkspaceLoaded to true later
1325 stopLoaderLocked();
1326 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1327 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1328 }
1329 }
1330
1331 /**
1332 * When the launcher is in the background, it's possible for it to miss paired
1333 * configuration changes. So whenever we trigger the loader from the background
1334 * tell the launcher that it needs to re-run the loader when it comes back instead
1335 * of doing it now.
1336 */
1337 public void startLoaderFromBackground() {
1338 boolean runLoader = false;
1339 if (mCallbacks != null) {
1340 Callbacks callbacks = mCallbacks.get();
1341 if (callbacks != null) {
1342 // Only actually run the loader if they're not paused.
1343 if (!callbacks.setLoadOnResume()) {
1344 runLoader = true;
1345 }
1346 }
1347 }
1348 if (runLoader) {
1349 startLoader(false, PagedView.INVALID_RESTORE_PAGE);
1350 }
1351 }
1352
1353 // If there is already a loader task running, tell it to stop.
1354 // returns true if isLaunching() was true on the old task
1355 private boolean stopLoaderLocked() {
1356 boolean isLaunching = false;
1357 LoaderTask oldTask = mLoaderTask;
1358 if (oldTask != null) {
1359 if (oldTask.isLaunching()) {
1360 isLaunching = true;
1361 }
1362 oldTask.stopLocked();
1363 }
1364 return isLaunching;
1365 }
1366
1367 public void startLoader(boolean isLaunching, int synchronousBindPage) {
1368 startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE);
1369 }
1370
1371 public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) {
1372 synchronized (mLock) {
1373 if (DEBUG_LOADERS) {
1374 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
1375 }
1376
1377 // Clear any deferred bind-runnables from the synchronized load process
1378 // We must do this before any loading/binding is scheduled below.
1379 mDeferredBindRunnables.clear();
1380
1381 // Don't bother to start the thread if we know it's not going to do anything
1382 if (mCallbacks != null && mCallbacks.get() != null) {
1383 // If there is already one running, tell it to stop.
1384 // also, don't downgrade isLaunching if we're already running
1385 isLaunching = isLaunching || stopLoaderLocked();
1386 mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags);
1387 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
1388 && mAllAppsLoaded && mWorkspaceLoaded) {
1389 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1390 } else {
1391 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1392 sWorker.post(mLoaderTask);
1393 }
1394 }
1395 }
1396 }
1397
1398 void bindRemainingSynchronousPages() {
1399 // Post the remaining side pages to be loaded
1400 if (!mDeferredBindRunnables.isEmpty()) {
1401 for (final Runnable r : mDeferredBindRunnables) {
1402 mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
1403 }
1404 mDeferredBindRunnables.clear();
1405 }
1406 }
1407
1408 public void stopLoader() {
1409 synchronized (mLock) {
1410 if (mLoaderTask != null) {
1411 mLoaderTask.stopLocked();
1412 }
1413 }
1414 }
1415
1416 /** Loads the workspace screens db into a map of Rank -> ScreenId */
1417 private static TreeMap<Integer, Long> loadWorkspaceScreensDb(Context context) {
1418 final ContentResolver contentResolver = context.getContentResolver();
1419 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1420 final Cursor sc = contentResolver.query(screensUri, null, null, null, null);
1421 TreeMap<Integer, Long> orderedScreens = new TreeMap<Integer, Long>();
1422
1423 try {
1424 final int idIndex = sc.getColumnIndexOrThrow(
1425 LauncherSettings.WorkspaceScreens._ID);
1426 final int rankIndex = sc.getColumnIndexOrThrow(
1427 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
1428 while (sc.moveToNext()) {
1429 try {
1430 long screenId = sc.getLong(idIndex);
1431 int rank = sc.getInt(rankIndex);
1432 orderedScreens.put(rank, screenId);
1433 } catch (Exception e) {
1434 Launcher.addDumpLog(TAG, "Desktop items loading interrupted - invalid screens: " + e,🔵
1435 }
1436 }
1437 } finally {
1438 sc.close();
1439 }
1440
1441 // Log to disk
1442 Launcher.addDumpLog(TAG, "11683562 - loadWorkspaceScreensDb()", true);
1443 ArrayList<String> orderedScreensPairs= new ArrayList<String>();
1444 for (Integer i : orderedScreens.keySet()) {
1445 orderedScreensPairs.add("{ " + i + ": " + orderedScreens.get(i) + " }");
1446 }
1447 Launcher.addDumpLog(TAG, "11683562 - screens: " +
1448 TextUtils.join(", ", orderedScreensPairs), true);
1449 return orderedScreens;
1450 }
1451
1452 public boolean isAllAppsLoaded() {
1453 return mAllAppsLoaded;
1454 }
1455
1456 boolean isLoadingWorkspace() {
1457 synchronized (mLock) {
1458 if (mLoaderTask != null) {
1459 return mLoaderTask.isLoadingWorkspace();
1460 }
1461 }
1462 return false;
1463 }
1464
1465 /**
1466 * Runnable for the thread that loads the contents of the launcher:
1467 * - workspace icons
1468 * - widgets
1469 * - all apps icons
1470 */
1471 private class LoaderTask implements Runnable {
1472 private Context mContext;
1473 private boolean mIsLaunching;
1474 private boolean mIsLoadingAndBindingWorkspace;
1475 private boolean mStopped;
1476 private boolean mLoadAndBindStepFinished;
1477 private int mFlags;
1478
1479 private HashMap<Object, CharSequence> mLabelCache;
1480
1481 LoaderTask(Context context, boolean isLaunching, int flags) {
1482 mContext = context;
1483 mIsLaunching = isLaunching;
1484 mLabelCache = new HashMap<Object, CharSequence>();
1485 mFlags = flags;
1486 }
1487
1488 boolean isLaunching() {
1489 return mIsLaunching;
1490 }
1491
1492 boolean isLoadingWorkspace() {
1493 return mIsLoadingAndBindingWorkspace;
1494 }
1495
1496 /** Returns whether this is an upgrade path */
1497 private boolean loadAndBindWorkspace() {
1498 mIsLoadingAndBindingWorkspace = true;
1499
1500 // Load the workspace
1501 if (DEBUG_LOADERS) {
1502 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1503 }
1504
1505 boolean isUpgradePath = false;
1506 if (!mWorkspaceLoaded) {
1507 isUpgradePath = loadWorkspace();
1508 synchronized (LoaderTask.this) {
1509 if (mStopped) {
1510 return isUpgradePath;
1511 }
1512 mWorkspaceLoaded = true;
1513 }
1514 }
1515
1516 // Bind the workspace
1517 bindWorkspace(-1, isUpgradePath);
1518 return isUpgradePath;
1519 }
1520
1521 private void waitForIdle() {
1522 // Wait until the either we're stopped or the other threads are done.
1523 // This way we don't start loading all apps until the workspace has settled
1524 // down.
1525 synchronized (LoaderTask.this) {
1526 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1527
1528 mHandler.postIdle(new Runnable() {
1529 public void run() {
1530 synchronized (LoaderTask.this) {
1531 mLoadAndBindStepFinished = true;
1532 if (DEBUG_LOADERS) {
1533 Log.d(TAG, "done with previous binding step");
1534 }
1535 LoaderTask.this.notify();
1536 }
1537 }
1538 });
1539
1540 while (!mStopped && !mLoadAndBindStepFinished && !mFlushingWorkerThread) {
1541 try {
1542 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1543 // wait no longer than 1sec at a time
1544 this.wait(1000);
1545 } catch (InterruptedException ex) {
1546 // Ignore
1547 }
1548 }
1549 if (DEBUG_LOADERS) {
1550 Log.d(TAG, "waited "
1551 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1552 + "ms for previous step to finish binding");
1553 }
1554 }
1555 }
1556
1557 void runBindSynchronousPage(int synchronousBindPage) {
1558 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1559 // Ensure that we have a valid page index to load synchronously
1560 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1561 "valid page index");
1562 }
1563 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1564 // Ensure that we don't try and bind a specified page when the pages have not been
1565 // loaded already (we should load everything asynchronously in that case)
1566 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1567 }
1568 synchronized (mLock) {
1569 if (mIsLoaderTaskRunning) {
1570 // Ensure that we are never running the background loading at this point since
1571 // we also touch the background collections
1572 throw new RuntimeException("Error! Background loading is already running");
1573 }
1574 }
1575
1576 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1577 // data structures, we can't allow any other thread to touch that data, but because
1578 // this call is synchronous, we can get away with not locking).
1579
1580 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1581 // operations from the previous activity. We need to ensure that all queued operations
1582 // are executed before any synchronous binding work is done.
1583 mHandler.flush();
1584
1585 // Divide the set of loaded items into those that we are binding synchronously, and
1586 // everything else that is to be bound normally (asynchronously).
1587 bindWorkspace(synchronousBindPage, false);
1588 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1589 // arise from that.
1590 onlyBindAllApps();
1591 }
1592
1593 public void run() {
1594 boolean isUpgrade = false;
1595
1596 synchronized (mLock) {
1597 mIsLoaderTaskRunning = true;
1598 }
1599 // Optimize for end-user experience: if the Launcher is up and // running with the
1600 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1601 // workspace first (default).
1602 keep_running: {
1603 // Elevate priority when Home launches for the first time to avoid
1604 // starving at boot time. Staring at a blank home is not cool.
1605 synchronized (mLock) {
1606 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
1607 (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
1608 android.os.Process.setThreadPriority(mIsLaunching
1609 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
1610 }
1611 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1612 isUpgrade = loadAndBindWorkspace();
1613
1614 if (mStopped) {
1615 break keep_running;
1616 }
1617
1618 // Whew! Hard work done. Slow us down, and wait until the UI thread has
1619 // settled down.
1620 synchronized (mLock) {
1621 if (mIsLaunching) {
1622 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
1623 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
1624 }
1625 }
1626 waitForIdle();
1627
1628 // second step
1629 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1630 loadAndBindAllApps();
1631
1632 // Restore the default thread priority after we are done loading items
1633 synchronized (mLock) {
1634 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
1635 }
1636 }
1637
1638 // Update the saved icons if necessary
1639 if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
1640 synchronized (sBgLock) {
1641 for (Object key : sBgDbIconCache.keySet()) {
1642 updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
1643 }
1644 sBgDbIconCache.clear();
1645 }
1646
1647 if (LauncherAppState.isDisableAllApps()) {
1648 // Ensure that all the applications that are in the system are
1649 // represented on the home screen.
1650 if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) {
1651 verifyApplications();
1652 }
1653 }
1654
1655 // Clear out this reference, otherwise we end up holding it until all of the
1656 // callback runnables are done.
1657 mContext = null;
1658
1659 synchronized (mLock) {
1660 // If we are still the last one to be scheduled, remove ourselves.
1661 if (mLoaderTask == this) {
1662 mLoaderTask = null;
1663 }
1664 mIsLoaderTaskRunning = false;
1665 }
1666 }
1667
1668 public void stopLocked() {
1669 synchronized (LoaderTask.this) {
1670 mStopped = true;
1671 this.notify();
1672 }
1673 }
1674
1675 /**
1676 * Gets the callbacks object. If we've been stopped, or if the launcher object
1677 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1678 * object that was around when the deferred message was scheduled, and if there's
1679 * a new Callbacks object around then also return null. This will save us from
1680 * calling onto it with data that will be ignored.
1681 */
1682 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1683 synchronized (mLock) {
1684 if (mStopped) {
1685 return null;
1686 }
1687
1688 if (mCallbacks == null) {
1689 return null;
1690 }
1691
1692 final Callbacks callbacks = mCallbacks.get();
1693 if (callbacks != oldCallbacks) {
1694 return null;
1695 }
1696 if (callbacks == null) {
1697 Log.w(TAG, "no mCallbacks");
1698 return null;
1699 }
1700
1701 return callbacks;
1702 }
1703 }
1704
1705 private void verifyApplications() {
1706 final Context context = mApp.getContext();
1707
1708 // Cross reference all the applications in our apps list with items in the workspace
1709 ArrayList<ItemInfo> tmpInfos;
1710 ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
1711 synchronized (sBgLock) {
1712 for (AppInfo app : mBgAllAppsList.data) {
1713 tmpInfos = getItemInfoForComponentName(app.componentName);
1714 if (tmpInfos.isEmpty()) {
1715 // We are missing an application icon, so add this to the workspace
1716 added.add(app);
1717 // This is a rare event, so lets log it
1718 Log.e(TAG, "Missing Application on load: " + app);
1719 }
1720 }
1721 }
1722 if (!added.isEmpty()) {
1723 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
1724 addAndBindAddedApps(context, added, cb, new ArrayList<AppInfo>());
1725 }
1726 }
1727
1728 // check & update map of what's occupied; used to discard overlapping/invalid items
1729 private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item,
1730 AtomicBoolean deleteOnInvalidPlacement) {
1731 LauncherAppState app = LauncherAppState.getInstance();
1732 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1733 final int countX = (int) grid.numColumns;
1734 final int countY = (int) grid.numRows;
1735
1736 long containerIndex = item.screenId;
1737 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1738 // Return early if we detect that an item is under the hotseat button
1739 if (mCallbacks == null ||
1740 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
1741 deleteOnInvalidPlacement.set(true);
1742 Log.e(TAG, "Error loading shortcut into hotseat " + item
1743 + " into position (" + item.screenId + ":" + item.cellX + ","
1744 + item.cellY + ") occupied by all apps");
1745 return false;
1746 }
1747
1748 final ItemInfo[][] hotseatItems =
1749 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1750
1751 if (item.screenId >= grid.numHotseatIcons) {
1752 Log.e(TAG, "Error loading shortcut " + item
1753 + " into hotseat position " + item.screenId
1754 + ", position out of bounds: (0 to " + (grid.numHotseatIcons - 1)
1755 + ")");
1756 return false;
1757 }
1758
1759 if (hotseatItems != null) {
1760 if (hotseatItems[(int) item.screenId][0] != null) {
1761 Log.e(TAG, "Error loading shortcut into hotseat " + item
1762 + " into position (" + item.screenId + ":" + item.cellX + ","
1763 + item.cellY + ") occupied by "
1764 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
1765 [(int) item.screenId][0]);
1766 return false;
1767 } else {
1768 hotseatItems[(int) item.screenId][0] = item;
1769 return true;
1770 }
1771 } else {
1772 final ItemInfo[][] items = new ItemInfo[(int) grid.numHotseatIcons][1];
1773 items[(int) item.screenId][0] = item;
1774 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
1775 return true;
1776 }
1777 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1778 // Skip further checking if it is not the hotseat or workspace container
1779 return true;
1780 }
1781
1782 if (!occupied.containsKey(item.screenId)) {
1783 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1784 occupied.put(item.screenId, items);
1785 }
1786
1787 final ItemInfo[][] screens = occupied.get(item.screenId);
1788 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1789 item.cellX < 0 || item.cellY < 0 ||
1790 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1791 Log.e(TAG, "Error loading shortcut " + item
1792 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1793 + item.cellX + "," + item.cellY
1794 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1795 return false;
1796 }
1797
1798 // Check if any workspace icons overlap with each other
1799 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1800 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1801 if (screens[x][y] != null) {
1802 Log.e(TAG, "Error loading shortcut " + item
1803 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1804 + x + "," + y
1805 + ") occupied by "
1806 + screens[x][y]);
1807 return false;
1808 }
1809 }
1810 }
1811 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1812 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1813 screens[x][y] = item;
1814 }
1815 }
1816
1817 return true;
1818 }
1819
1820 /** Clears all the sBg data structures */
1821 private void clearSBgDataStructures() {
1822 synchronized (sBgLock) {
1823 sBgWorkspaceItems.clear();
1824 sBgAppWidgets.clear();
1825 sBgFolders.clear();
1826 sBgItemsIdMap.clear();
1827 sBgDbIconCache.clear();
1828 sBgWorkspaceScreens.clear();
1829 }
1830 }
1831
1832 /** Returns whether this is an upgrade path */
1833 private boolean loadWorkspace() {
1834 // Log to disk
1835 Launcher.addDumpLog(TAG, "11683562 - loadWorkspace()", true);
1836
1837 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1838
1839 final Context context = mContext;
1840 final ContentResolver contentResolver = context.getContentResolver();
1841 final PackageManager manager = context.getPackageManager();
1842 final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
1843 final boolean isSafeMode = manager.isSafeMode();
1844
1845 LauncherAppState app = LauncherAppState.getInstance();
1846 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1847 int countX = (int) grid.numColumns;
1848 int countY = (int) grid.numRows;
1849
1850 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1851 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1852 LauncherAppState.getLauncherProvider().deleteDatabase();
1853 }
1854
1855 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1856 // append the user's Launcher2 shortcuts
1857 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1858 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1859 } else {
1860 // Make sure the default workspace is loaded
1861 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1862 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
1863 }
1864
1865 // Check if we need to do any upgrade-path logic
1866 // (Includes having just imported default favorites)
1867 boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb();
1868
1869 // Log to disk
1870 Launcher.addDumpLog(TAG, "11683562 - loadedOldDb: " + loadedOldDb, true);
1871
1872 synchronized (sBgLock) {
1873 clearSBgDataStructures();
1874
1875 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1876 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1877 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1878 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
1879 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1880
1881 // +1 for the hotseat (it can be larger than the workspace)
1882 // Load workspace in reverse order to ensure that latest items are loaded first (and
1883 // before any earlier duplicates)
1884 final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>();
1885
1886 try {
1887 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1888 final int intentIndex = c.getColumnIndexOrThrow
1889 (LauncherSettings.Favorites.INTENT);
1890 final int titleIndex = c.getColumnIndexOrThrow
1891 (LauncherSettings.Favorites.TITLE);
1892 final int iconTypeIndex = c.getColumnIndexOrThrow(
1893 LauncherSettings.Favorites.ICON_TYPE);
1894 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1895 final int iconPackageIndex = c.getColumnIndexOrThrow(
1896 LauncherSettings.Favorites.ICON_PACKAGE);
1897 final int iconResourceIndex = c.getColumnIndexOrThrow(
1898 LauncherSettings.Favorites.ICON_RESOURCE);
1899 final int containerIndex = c.getColumnIndexOrThrow(
1900 LauncherSettings.Favorites.CONTAINER);
1901 final int itemTypeIndex = c.getColumnIndexOrThrow(
1902 LauncherSettings.Favorites.ITEM_TYPE);
1903 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1904 LauncherSettings.Favorites.APPWIDGET_ID);
1905 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1906 LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1907 final int screenIndex = c.getColumnIndexOrThrow(
1908 LauncherSettings.Favorites.SCREEN);
1909 final int cellXIndex = c.getColumnIndexOrThrow
1910 (LauncherSettings.Favorites.CELLX);
1911 final int cellYIndex = c.getColumnIndexOrThrow
1912 (LauncherSettings.Favorites.CELLY);
1913 final int spanXIndex = c.getColumnIndexOrThrow
1914 (LauncherSettings.Favorites.SPANX);
1915 final int spanYIndex = c.getColumnIndexOrThrow(
1916 LauncherSettings.Favorites.SPANY);
1917 final int restoredIndex = c.getColumnIndexOrThrow(
1918 LauncherSettings.Favorites.RESTORED);
1919 //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1920 //final int displayModeIndex = c.getColumnIndexOrThrow(
1921 // LauncherSettings.Favorites.DISPLAY_MODE);
1922
1923 ShortcutInfo info;
1924 String intentDescription;
1925 LauncherAppWidgetInfo appWidgetInfo;
1926 int container;
1927 long id;
1928 Intent intent;
1929
1930 while (!mStopped && c.moveToNext()) {
1931 AtomicBoolean deleteOnInvalidPlacement = new AtomicBoolean(false);
1932 try {
1933 int itemType = c.getInt(itemTypeIndex);
1934 boolean restored = 0 != c.getInt(restoredIndex);
1935
1936 switch (itemType) {
1937 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1938 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1939 id = c.getLong(idIndex);
1940 intentDescription = c.getString(intentIndex);
1941 try {
1942 intent = Intent.parseUri(intentDescription, 0);
1943 ComponentName cn = intent.getComponent();
1944 if (cn != null && !isValidPackageComponent(manager, cn)) {
1945 if (restored) {
1946 // might be installed later
1947 Launcher.addDumpLog(TAG,
1948 "package not yet restored: " + cn, true);
1949 } else {
1950 if (!mAppsCanBeOnRemoveableStorage) {
1951 // Log the invalid package, and remove it
1952 Launcher.addDumpLog(TAG,
1953 "Invalid package removed: " + cn, true);
1954 itemsToRemove.add(id);
1955 } else {
1956 // If apps can be on external storage, then we just
1957 // leave them for the user to remove (maybe add
1958 // visual treatment to it)
1959 Launcher.addDumpLog(TAG,
1960 "Invalid package found: " + cn, true);
1961 }
1962 continue;
1963 }
1964 } else if (restored) {
1965 // no special handling necessary for this restored item
1966 restoredRows.add(id);
1967 restored = false;
1968 }
1969 } catch (URISyntaxException e) {
1970 Launcher.addDumpLog(TAG,
1971 "Invalid uri: " + intentDescription, true);
1972 continue;
1973 }
1974
1975 if (restored) {
1976 Launcher.addDumpLog(TAG,
1977 "constructing info for partially restored package",
1978 true);
1979 info = getRestoredItemInfo(c, titleIndex, intent);
1980 intent = getRestoredItemIntent(c, context, intent);
1981 } else if (itemType ==
1982 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
1983 info = getShortcutInfo(manager, intent, context, c, iconIndex,
1984 titleIndex, mLabelCache);
1985 } else {
1986 info = getShortcutInfo(c, context, iconTypeIndex,
1987 iconPackageIndex, iconResourceIndex, iconIndex,
1988 titleIndex);
1989
1990 // App shortcuts that used to be automatically added to Launcher
1991 // didn't always have the correct intent flags set, so do that
1992 // here
1993 if (intent.getAction() != null &&
1994 intent.getCategories() != null &&
1995 intent.getAction().equals(Intent.ACTION_MAIN) &&
1996 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
1997 intent.addFlags(
1998 Intent.FLAG_ACTIVITY_NEW_TASK |
1999 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
2000 }
2001 }
2002
2003 if (info != null) {
2004 info.id = id;
2005 info.intent = intent;
2006 container = c.getInt(containerIndex);
2007 info.container = container;
2008 info.screenId = c.getInt(screenIndex);
2009 info.cellX = c.getInt(cellXIndex);
2010 info.cellY = c.getInt(cellYIndex);
2011 info.spanX = 1;
2012 info.spanY = 1;
2013
2014 // check & update map of what's occupied
2015 deleteOnInvalidPlacement.set(false);
2016 if (!checkItemPlacement(occupied, info, deleteOnInvalidPlacement)) {
2017 if (deleteOnInvalidPlacement.get()) {
2018 itemsToRemove.add(id);
2019 }
2020 break;
2021 }
2022
2023 switch (container) {
2024 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2025 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2026 sBgWorkspaceItems.add(info);
2027 break;
2028 default:
2029 // Item is in a user folder
2030 FolderInfo folderInfo =
2031 findOrMakeFolder(sBgFolders, container);
2032 folderInfo.add(info);
2033 break;
2034 }
2035 sBgItemsIdMap.put(info.id, info);
2036
2037 // now that we've loaded everthing re-save it with the
2038 // icon in case it disappears somehow.
2039 queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
2040 } else {
2041 throw new RuntimeException("Unexpected null ShortcutInfo");
2042 }
2043 break;
2044
2045 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2046 id = c.getLong(idIndex);
2047 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2048
2049 folderInfo.title = c.getString(titleIndex);
2050 folderInfo.id = id;
2051 container = c.getInt(containerIndex);
2052 folderInfo.container = container;
2053 folderInfo.screenId = c.getInt(screenIndex);
2054 folderInfo.cellX = c.getInt(cellXIndex);
2055 folderInfo.cellY = c.getInt(cellYIndex);
2056 folderInfo.spanX = 1;
2057 folderInfo.spanY = 1;
2058
2059 // check & update map of what's occupied
2060 deleteOnInvalidPlacement.set(false);
2061 if (!checkItemPlacement(occupied, folderInfo,
2062 deleteOnInvalidPlacement)) {
2063 if (deleteOnInvalidPlacement.get()) {
2064 itemsToRemove.add(id);
2065 }
2066 break;
2067 }
2068
2069 switch (container) {
2070 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2071 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2072 sBgWorkspaceItems.add(folderInfo);
2073 break;
2074 }
2075
2076 if (restored) {
2077 // no special handling required for restored folders
2078 restoredRows.add(id);
2079 }
2080
2081 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2082 sBgFolders.put(folderInfo.id, folderInfo);
2083 break;
2084
2085 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2086 // Read all Launcher-specific widget details
2087 int appWidgetId = c.getInt(appWidgetIdIndex);
2088 String savedProvider = c.getString(appWidgetProviderIndex);
2089
2090 id = c.getLong(idIndex);
2091
2092 final AppWidgetProviderInfo provider =
2093 widgets.getAppWidgetInfo(appWidgetId);
2094
2095 if (!isSafeMode && (provider == null || provider.provider == null ||
2096 provider.provider.getPackageName() == null)) {
2097 String log = "Deleting widget that isn't installed anymore: id="
2098 + id + " appWidgetId=" + appWidgetId;
2099 Log.e(TAG, log);
2100 Launcher.addDumpLog(TAG, log, false);
2101 itemsToRemove.add(id);
2102 } else {
2103 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2104 provider.provider);
2105 appWidgetInfo.id = id;
2106 appWidgetInfo.screenId = c.getInt(screenIndex);
2107 appWidgetInfo.cellX = c.getInt(cellXIndex);
2108 appWidgetInfo.cellY = c.getInt(cellYIndex);
2109 appWidgetInfo.spanX = c.getInt(spanXIndex);
2110 appWidgetInfo.spanY = c.getInt(spanYIndex);
2111 int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
2112 appWidgetInfo.minSpanX = minSpan[0];
2113 appWidgetInfo.minSpanY = minSpan[1];
2114
2115 container = c.getInt(containerIndex);
2116 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2117 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2118 Log.e(TAG, "Widget found where container != " +
2119 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2120 continue;
2121 }
2122
2123 appWidgetInfo.container = c.getInt(containerIndex);
2124 // check & update map of what's occupied
2125 deleteOnInvalidPlacement.set(false);
2126 if (!checkItemPlacement(occupied, appWidgetInfo,
2127 deleteOnInvalidPlacement)) {
2128 if (deleteOnInvalidPlacement.get()) {
2129 itemsToRemove.add(id);
2130 }
2131 break;
2132 }
2133 String providerName = provider.provider.flattenToString();
2134 if (!providerName.equals(savedProvider)) {
2135 ContentValues values = new ContentValues();
2136 values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2137 providerName);
2138 String where = BaseColumns._ID + "= ?";
2139 String[] args = {Integer.toString(c.getInt(idIndex))};
2140 contentResolver.update(contentUri, values, where, args);
2141 }
2142 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2143 sBgAppWidgets.add(appWidgetInfo);
2144 }
2145 break;
2146 }
2147 } catch (Exception e) {
2148 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2149 }
2150 }
2151 } finally {
2152 if (c != null) {
2153 c.close();
2154 }
2155 }
2156
2157 // Break early if we've stopped loading
2158 if (mStopped) {
2159 clearSBgDataStructures();
2160 return false;
2161 }
2162
2163 if (itemsToRemove.size() > 0) {
2164 ContentProviderClient client = contentResolver.acquireContentProviderClient(
2165 LauncherSettings.Favorites.CONTENT_URI);
2166 // Remove dead items
2167 for (long id : itemsToRemove) {
2168 if (DEBUG_LOADERS) {
2169 Log.d(TAG, "Removed id = " + id);
2170 }
2171 // Don't notify content observers
2172 try {
2173 client.delete(LauncherSettings.Favorites.getContentUri(id, false),
2174 null, null);
2175 } catch (RemoteException e) {
2176 Log.w(TAG, "Could not remove id = " + id);
2177 }
2178 }
2179 }
2180
2181 if (restoredRows.size() > 0) {
2182 ContentProviderClient updater = contentResolver.acquireContentProviderClient(
2183 LauncherSettings.Favorites.CONTENT_URI);
2184 // Update restored items that no longer require special handling
2185 try {
2186 StringBuilder selectionBuilder = new StringBuilder();
2187 selectionBuilder.append(LauncherSettings.Favorites._ID);
2188 selectionBuilder.append(" IN (");
2189 selectionBuilder.append(TextUtils.join(", ", restoredRows));
2190 selectionBuilder.append(")");
2191 ContentValues values = new ContentValues();
2192 values.put(LauncherSettings.Favorites.RESTORED, 0);
2193 updater.update(LauncherSettings.Favorites.CONTENT_URI,
2194 values, selectionBuilder.toString(), null);
2195 } catch (RemoteException e) {
2196 Log.w(TAG, "Could not update restored rows");
2197 }
2198 }
2199
2200 if (loadedOldDb) {
2201 long maxScreenId = 0;
2202 // If we're importing we use the old screen order.
2203 for (ItemInfo item: sBgItemsIdMap.values()) {
2204 long screenId = item.screenId;
2205 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2206 !sBgWorkspaceScreens.contains(screenId)) {
2207 sBgWorkspaceScreens.add(screenId);
2208 if (screenId > maxScreenId) {
2209 maxScreenId = screenId;
2210 }
2211 }
2212 }
2213 Collections.sort(sBgWorkspaceScreens);
2214 // Log to disk
2215 Launcher.addDumpLog(TAG, "11683562 - maxScreenId: " + maxScreenId, true);
2216 Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
2217 TextUtils.join(", ", sBgWorkspaceScreens), true);
2218
2219 LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId);
2220 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2221
2222 // Update the max item id after we load an old db
2223 long maxItemId = 0;
2224 // If we're importing we use the old screen order.
2225 for (ItemInfo item: sBgItemsIdMap.values()) {
2226 maxItemId = Math.max(maxItemId, item.id);
2227 }
2228 LauncherAppState.getLauncherProvider().updateMaxItemId(maxItemId);
2229 } else {
2230 TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(mContext);
2231 for (Integer i : orderedScreens.keySet()) {
2232 sBgWorkspaceScreens.add(orderedScreens.get(i));
2233 }
2234 // Log to disk
2235 Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
2236 TextUtils.join(", ", sBgWorkspaceScreens), true);
2237
2238 // Remove any empty screens
2239 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2240 for (ItemInfo item: sBgItemsIdMap.values()) {
2241 long screenId = item.screenId;
2242 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2243 unusedScreens.contains(screenId)) {
2244 unusedScreens.remove(screenId);
2245 }
2246 }
2247
2248 // If there are any empty screens remove them, and update.
2249 if (unusedScreens.size() != 0) {
2250 // Log to disk
2251 Launcher.addDumpLog(TAG, "11683562 - unusedScreens (to be removed): " +
2252 TextUtils.join(", ", unusedScreens), true);
2253
2254 sBgWorkspaceScreens.removeAll(unusedScreens);
2255 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2256 }
2257 }
2258
2259 if (DEBUG_LOADERS) {
2260 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
2261 Log.d(TAG, "workspace layout: ");
2262 int nScreens = occupied.size();
2263 for (int y = 0; y < countY; y++) {
2264 String line = "";
2265
2266 Iterator<Long> iter = occupied.keySet().iterator();
2267 while (iter.hasNext()) {
2268 long screenId = iter.next();
2269 if (screenId > 0) {
2270 line += " | ";
2271 }
2272 for (int x = 0; x < countX; x++) {
2273 line += ((occupied.get(screenId)[x][y] != null) ? "#" : ".");
2274 }
2275 }
2276 Log.d(TAG, "[ " + line + " ]");
2277 }
2278 }
2279 }
2280 return loadedOldDb;
2281 }
2282
2283 /** Filters the set of items who are directly or indirectly (via another container) on the
2284 * specified screen. */
2285 private void filterCurrentWorkspaceItems(long currentScreenId,
2286 ArrayList<ItemInfo> allWorkspaceItems,
2287 ArrayList<ItemInfo> currentScreenItems,
2288 ArrayList<ItemInfo> otherScreenItems) {
2289 // Purge any null ItemInfos
2290 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2291 while (iter.hasNext()) {
2292 ItemInfo i = iter.next();
2293 if (i == null) {
2294 iter.remove();
2295 }
2296 }
2297
2298 // Order the set of items by their containers first, this allows use to walk through the
2299 // list sequentially, build up a list of containers that are in the specified screen,
2300 // as well as all items in those containers.
2301 Set<Long> itemsOnScreen = new HashSet<Long>();
2302 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2303 @Override
2304 public int compare(ItemInfo lhs, ItemInfo rhs) {
2305 return (int) (lhs.container - rhs.container);
2306 }
2307 });
2308 for (ItemInfo info : allWorkspaceItems) {
2309 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2310 if (info.screenId == currentScreenId) {
2311 currentScreenItems.add(info);
2312 itemsOnScreen.add(info.id);
2313 } else {
2314 otherScreenItems.add(info);
2315 }
2316 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2317 currentScreenItems.add(info);
2318 itemsOnScreen.add(info.id);
2319 } else {
2320 if (itemsOnScreen.contains(info.container)) {
2321 currentScreenItems.add(info);
2322 itemsOnScreen.add(info.id);
2323 } else {
2324 otherScreenItems.add(info);
2325 }
2326 }
2327 }
2328 }
2329
2330 /** Filters the set of widgets which are on the specified screen. */
2331 private void filterCurrentAppWidgets(long currentScreenId,
2332 ArrayList<LauncherAppWidgetInfo> appWidgets,
2333 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2334 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2335
2336 for (LauncherAppWidgetInfo widget : appWidgets) {
2337 if (widget == null) continue;
2338 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2339 widget.screenId == currentScreenId) {
2340 currentScreenWidgets.add(widget);
2341 } else {
2342 otherScreenWidgets.add(widget);
2343 }
2344 }
2345 }
2346
2347 /** Filters the set of folders which are on the specified screen. */
2348 private void filterCurrentFolders(long currentScreenId,
2349 HashMap<Long, ItemInfo> itemsIdMap,
2350 HashMap<Long, FolderInfo> folders,
2351 HashMap<Long, FolderInfo> currentScreenFolders,
2352 HashMap<Long, FolderInfo> otherScreenFolders) {
2353
2354 for (long id : folders.keySet()) {
2355 ItemInfo info = itemsIdMap.get(id);
2356 FolderInfo folder = folders.get(id);
2357 if (info == null || folder == null) continue;
2358 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2359 info.screenId == currentScreenId) {
2360 currentScreenFolders.put(id, folder);
2361 } else {
2362 otherScreenFolders.put(id, folder);
2363 }
2364 }
2365 }
2366
2367 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2368 * right) */
2369 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2370 final LauncherAppState app = LauncherAppState.getInstance();
2371 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2372 // XXX: review this
2373 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2374 @Override
2375 public int compare(ItemInfo lhs, ItemInfo rhs) {
2376 int cellCountX = (int) grid.numColumns;
2377 int cellCountY = (int) grid.numRows;
2378 int screenOffset = cellCountX * cellCountY;
2379 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
2380 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
2381 lhs.cellY * cellCountX + lhs.cellX);
2382 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
2383 rhs.cellY * cellCountX + rhs.cellX);
2384 return (int) (lr - rr);
2385 }
2386 });
2387 }
2388
2389 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2390 final ArrayList<Long> orderedScreens) {
2391 final Runnable r = new Runnable() {
2392 @Override
2393 public void run() {
2394 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2395 if (callbacks != null) {
2396 callbacks.bindScreens(orderedScreens);
2397 }
2398 }
2399 };
2400 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2401 }
2402
2403 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2404 final ArrayList<ItemInfo> workspaceItems,
2405 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2406 final HashMap<Long, FolderInfo> folders,
2407 ArrayList<Runnable> deferredBindRunnables) {
2408
2409 final boolean postOnMainThread = (deferredBindRunnables != null);
2410
2411 // Bind the workspace items
2412 int N = workspaceItems.size();
2413 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2414 final int start = i;
2415 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2416 final Runnable r = new Runnable() {
2417 @Override
2418 public void run() {
2419 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2420 if (callbacks != null) {
2421 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2422 false);
2423 }
2424 }
2425 };
2426 if (postOnMainThread) {
2427 deferredBindRunnables.add(r);
2428 } else {
2429 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2430 }
2431 }
2432
2433 // Bind the folders
2434 if (!folders.isEmpty()) {
2435 final Runnable r = new Runnable() {
2436 public void run() {
2437 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2438 if (callbacks != null) {
2439 callbacks.bindFolders(folders);
2440 }
2441 }
2442 };
2443 if (postOnMainThread) {
2444 deferredBindRunnables.add(r);
2445 } else {
2446 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2447 }
2448 }
2449
2450 // Bind the widgets, one at a time
2451 N = appWidgets.size();
2452 for (int i = 0; i < N; i++) {
2453 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2454 final Runnable r = new Runnable() {
2455 public void run() {
2456 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2457 if (callbacks != null) {
2458 callbacks.bindAppWidget(widget);
2459 }
2460 }
2461 };
2462 if (postOnMainThread) {
2463 deferredBindRunnables.add(r);
2464 } else {
2465 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2466 }
2467 }
2468 }
2469
2470 /**
2471 * Binds all loaded data to actual views on the main thread.
2472 */
2473 private void bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath) {
2474 final long t = SystemClock.uptimeMillis();
2475 Runnable r;
2476
2477 // Don't use these two variables in any of the callback runnables.
2478 // Otherwise we hold a reference to them.
2479 final Callbacks oldCallbacks = mCallbacks.get();
2480 if (oldCallbacks == null) {
2481 // This launcher has exited and nobody bothered to tell us. Just bail.
2482 Log.w(TAG, "LoaderTask running with no launcher");
2483 return;
2484 }
2485
2486 // Save a copy of all the bg-thread collections
2487 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2488 ArrayList<LauncherAppWidgetInfo> appWidgets =
2489 new ArrayList<LauncherAppWidgetInfo>();
2490 HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
2491 HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
2492 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2493 synchronized (sBgLock) {
2494 workspaceItems.addAll(sBgWorkspaceItems);
2495 appWidgets.addAll(sBgAppWidgets);
2496 folders.putAll(sBgFolders);
2497 itemsIdMap.putAll(sBgItemsIdMap);
2498 orderedScreenIds.addAll(sBgWorkspaceScreens);
2499 }
2500
2501 final boolean isLoadingSynchronously =
2502 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2503 int currScreen = isLoadingSynchronously ? synchronizeBindPage :
2504 oldCallbacks.getCurrentWorkspaceScreen();
2505 if (currScreen >= orderedScreenIds.size()) {
2506 // There may be no workspace screens (just hotseat items and an empty page).
2507 currScreen = PagedView.INVALID_RESTORE_PAGE;
2508 }
2509 final int currentScreen = currScreen;
2510 final long currentScreenId = currentScreen < 0
2511 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
2512
2513 // Load all the items that are on the current page first (and in the process, unbind
2514 // all the existing workspace items before we call startBinding() below.
2515 unbindWorkspaceItemsOnMainThread();
2516
2517 // Separate the items that are on the current screen, and all the other remaining items
2518 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2519 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2520 ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
2521 new ArrayList<LauncherAppWidgetInfo>();
2522 ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
2523 new ArrayList<LauncherAppWidgetInfo>();
2524 HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
2525 HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
2526
2527 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2528 otherWorkspaceItems);
2529 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2530 otherAppWidgets);
2531 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
2532 otherFolders);
2533 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2534 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2535
2536 // Tell the workspace that we're about to start binding items
2537 r = new Runnable() {
2538 public void run() {
2539 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2540 if (callbacks != null) {
2541 callbacks.startBinding();
2542 }
2543 }
2544 };
2545 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2546
2547 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2548
2549 // Load items on the current page
2550 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
2551 currentFolders, null);
2552 if (isLoadingSynchronously) {
2553 r = new Runnable() {
2554 public void run() {
2555 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2556 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2557 callbacks.onPageBoundSynchronously(currentScreen);
2558 }
2559 }
2560 };
2561 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2562 }
2563
2564 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2565 // work until after the first render)
2566 mDeferredBindRunnables.clear();
2567 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
2568 (isLoadingSynchronously ? mDeferredBindRunnables : null));
2569
2570 // Tell the workspace that we're done binding items
2571 r = new Runnable() {
2572 public void run() {
2573 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2574 if (callbacks != null) {
2575 callbacks.finishBindingItems(isUpgradePath);
2576 }
2577
2578 // If we're profiling, ensure this is the last thing in the queue.
2579 if (DEBUG_LOADERS) {
2580 Log.d(TAG, "bound workspace in "
2581 + (SystemClock.uptimeMillis()-t) + "ms");
2582 }
2583
2584 mIsLoadingAndBindingWorkspace = false;
2585 }
2586 };
2587 if (isLoadingSynchronously) {
2588 mDeferredBindRunnables.add(r);
2589 } else {
2590 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2591 }
2592 }
2593
2594 private void loadAndBindAllApps() {
2595 if (DEBUG_LOADERS) {
2596 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2597 }
2598 if (!mAllAppsLoaded) {
2599 loadAllApps();
2600 synchronized (LoaderTask.this) {
2601 if (mStopped) {
2602 return;
2603 }
2604 mAllAppsLoaded = true;
2605 }
2606 } else {
2607 onlyBindAllApps();
2608 }
2609 }
2610
2611 private void onlyBindAllApps() {
2612 final Callbacks oldCallbacks = mCallbacks.get();
2613 if (oldCallbacks == null) {
2614 // This launcher has exited and nobody bothered to tell us. Just bail.
2615 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2616 return;
2617 }
2618
2619 // shallow copy
2620 @SuppressWarnings("unchecked")
2621 final ArrayList<AppInfo> list
2622 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2623 Runnable r = new Runnable() {
2624 public void run() {
2625 final long t = SystemClock.uptimeMillis();
2626 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2627 if (callbacks != null) {
2628 callbacks.bindAllApplications(list);
2629 }
2630 if (DEBUG_LOADERS) {
2631 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2632 + (SystemClock.uptimeMillis()-t) + "ms");
2633 }
2634 }
2635 };
2636 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2637 if (isRunningOnMainThread) {
2638 r.run();
2639 } else {
2640 mHandler.post(r);
2641 }
2642 }
2643
2644 private void loadAllApps() {
2645 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2646
2647 final Callbacks oldCallbacks = mCallbacks.get();
2648 if (oldCallbacks == null) {
2649 // This launcher has exited and nobody bothered to tell us. Just bail.
2650 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2651 return;
2652 }
2653
2654 final PackageManager packageManager = mContext.getPackageManager();
2655 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
2656 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2657
2658 // Clear the list of apps
2659 mBgAllAppsList.clear();
2660
2661 // Query for the set of apps
2662 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2663 List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
2664 if (DEBUG_LOADERS) {
2665 Log.d(TAG, "queryIntentActivities took "
2666 + (SystemClock.uptimeMillis()-qiaTime) + "ms");
2667 Log.d(TAG, "queryIntentActivities got " + apps.size() + " apps");
2668 }
2669 // Fail if we don't have any apps
2670 if (apps == null || apps.isEmpty()) {
2671 return;
2672 }
2673 // Sort the applications by name
2674 final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2675 Collections.sort(apps,
2676 new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
2677 if (DEBUG_LOADERS) {
2678 Log.d(TAG, "sort took "
2679 + (SystemClock.uptimeMillis()-sortTime) + "ms");
2680 }
2681
2682 // Create the ApplicationInfos
2683 for (int i = 0; i < apps.size(); i++) {
2684 ResolveInfo app = apps.get(i);
2685 // This builds the icon bitmaps.
2686 mBgAllAppsList.add(new AppInfo(packageManager, app,
2687 mIconCache, mLabelCache));
2688 }
2689
2690 // Huh? Shouldn't this be inside the Runnable below?
2691 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2692 mBgAllAppsList.added = new ArrayList<AppInfo>();
2693
2694 // Post callback on main thread
2695 mHandler.post(new Runnable() {
2696 public void run() {
2697 final long bindTime = SystemClock.uptimeMillis();
2698 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2699 if (callbacks != null) {
2700 callbacks.bindAllApplications(added);
2701 if (DEBUG_LOADERS) {
2702 Log.d(TAG, "bound " + added.size() + " apps in "
2703 + (SystemClock.uptimeMillis() - bindTime) + "ms");
2704 }
2705 } else {
2706 Log.i(TAG, "not binding apps: no Launcher activity");
2707 }
2708 }
2709 });
2710
2711 if (DEBUG_LOADERS) {
2712 Log.d(TAG, "Icons processed in "
2713 + (SystemClock.uptimeMillis() - loadTime) + "ms");
2714 }
2715 }
2716
2717 public void dumpState() {
2718 synchronized (sBgLock) {
2719 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2720 Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
2721 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2722 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2723 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2724 }
2725 }
2726 }
2727
2728 void enqueuePackageUpdated(PackageUpdatedTask task) {
2729 sWorker.post(task);
2730 }
2731
2732 private class PackageUpdatedTask implements Runnable {
2733 int mOp;
2734 String[] mPackages;
2735
2736 public static final int OP_NONE = 0;
2737 public static final int OP_ADD = 1;
2738 public static final int OP_UPDATE = 2;
2739 public static final int OP_REMOVE = 3; // uninstlled
2740 public static final int OP_UNAVAILABLE = 4; // external media unmounted
2741
2742
2743 public PackageUpdatedTask(int op, String[] packages) {
2744 mOp = op;
2745 mPackages = packages;
2746 }
2747
2748 public void run() {
2749 final Context context = mApp.getContext();
2750
2751 final String[] packages = mPackages;
2752 final int N = packages.length;
2753 switch (mOp) {
2754 case OP_ADD:
2755 for (int i=0; i<N; i++) {
2756 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
2757 mBgAllAppsList.addPackage(context, packages[i]);
2758 }
2759 break;
2760 case OP_UPDATE:
2761 for (int i=0; i<N; i++) {
2762 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
2763 mBgAllAppsList.updatePackage(context, packages[i]);
2764 WidgetPreviewLoader.removePackageFromDb(
2765 mApp.getWidgetPreviewCacheDb(), packages[i]);
2766 }
2767 break;
2768 case OP_REMOVE:
2769 case OP_UNAVAILABLE:
2770 for (int i=0; i<N; i++) {
2771 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
2772 mBgAllAppsList.removePackage(packages[i]);
2773 WidgetPreviewLoader.removePackageFromDb(
2774 mApp.getWidgetPreviewCacheDb(), packages[i]);
2775 }
2776 break;
2777 }
2778
2779 ArrayList<AppInfo> added = null;
2780 ArrayList<AppInfo> modified = null;
2781 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
2782
2783 if (mBgAllAppsList.added.size() > 0) {
2784 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
2785 mBgAllAppsList.added.clear();
2786 }
2787 if (mBgAllAppsList.modified.size() > 0) {
2788 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
2789 mBgAllAppsList.modified.clear();
2790 }
2791 if (mBgAllAppsList.removed.size() > 0) {
2792 removedApps.addAll(mBgAllAppsList.removed);
2793 mBgAllAppsList.removed.clear();
2794 }
2795
2796 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
2797 if (callbacks == null) {
2798 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
2799 return;
2800 }
2801
2802 if (added != null) {
2803 // Ensure that we add all the workspace applications to the db
2804 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
2805 if (!LauncherAppState.isDisableAllApps()) {
2806 addAndBindAddedApps(context, new ArrayList<ItemInfo>(), cb, added);
2807 } else {
2808 final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added);
2809 addAndBindAddedApps(context, addedInfos, cb, added);
2810 }
2811 }
2812 if (modified != null) {
2813 final ArrayList<AppInfo> modifiedFinal = modified;
2814
2815 // Update the launcher db to reflect the changes
2816 for (AppInfo a : modifiedFinal) {
2817 ArrayList<ItemInfo> infos =
2818 getItemInfoForComponentName(a.componentName);
2819 for (ItemInfo i : infos) {
2820 if (isShortcutInfoUpdateable(i)) {
2821 ShortcutInfo info = (ShortcutInfo) i;
2822 info.title = a.title.toString();
2823 updateItemInDatabase(context, info);
2824 }
2825 }
2826 }
2827
2828 mHandler.post(new Runnable() {
2829 public void run() {
2830 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
2831 if (callbacks == cb && cb != null) {
2832 callbacks.bindAppsUpdated(modifiedFinal);
2833 }
2834 }
2835 });
2836 }
2837
2838 final ArrayList<String> removedPackageNames =
2839 new ArrayList<String>();
2840 if (mOp == OP_REMOVE) {
2841 // Mark all packages in the broadcast to be removed
2842 removedPackageNames.addAll(Arrays.asList(packages));
2843 } else if (mOp == OP_UPDATE) {
2844 // Mark disabled packages in the broadcast to be removed
2845 final PackageManager pm = context.getPackageManager();
2846 for (int i=0; i<N; i++) {
2847 if (isPackageDisabled(pm, packages[i])) {
2848 removedPackageNames.add(packages[i]);
2849 }
2850 }
2851 }
2852 // Remove all the components associated with this package
2853 for (String pn : removedPackageNames) {
2854 ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn);
2855 for (ItemInfo i : infos) {
2856 deleteItemFromDatabase(context, i);
2857 }
2858 }
2859 // Remove all the specific components
2860 for (AppInfo a : removedApps) {
2861 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName);
2862 for (ItemInfo i : infos) {
2863 deleteItemFromDatabase(context, i);
2864 }
2865 }
2866 if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
2867 // Remove any queued items from the install queue
2868 String spKey = LauncherAppState.getSharedPreferencesKey();
2869 SharedPreferences sp =
2870 context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
2871 InstallShortcutReceiver.removeFromInstallQueue(sp, removedPackageNames);
2872 // Call the components-removed callback
2873 mHandler.post(new Runnable() {
2874 public void run() {
2875 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
2876 if (callbacks == cb && cb != null) {
2877 callbacks.bindComponentsRemoved(removedPackageNames, removedApps);
2878 }
2879 }
2880 });
2881 }
2882
2883 final ArrayList<Object> widgetsAndShortcuts =
2884 getSortedWidgetsAndShortcuts(context);
2885 mHandler.post(new Runnable() {
2886 @Override
2887 public void run() {
2888 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
2889 if (callbacks == cb && cb != null) {
2890 callbacks.bindPackagesUpdated(widgetsAndShortcuts);
2891 }
2892 }
2893 });
2894
2895 // Write all the logs to disk
2896 mHandler.post(new Runnable() {
2897 public void run() {
2898 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
2899 if (callbacks == cb && cb != null) {
2900 callbacks.dumpLogsToLocalData();
2901 }
2902 }
2903 });
2904 }
2905 }
2906
2907 // Returns a list of ResolveInfos/AppWindowInfos in sorted order
2908 public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context) {
2909 PackageManager packageManager = context.getPackageManager();
2910 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
2911 widgetsAndShortcuts.addAll(AppWidgetManager.getInstance(context).getInstalledProviders());
2912 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2913 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
2914 Collections.sort(widgetsAndShortcuts,
2915 new LauncherModel.WidgetAndShortcutNameComparator(packageManager));
2916 return widgetsAndShortcuts;
2917 }
2918
2919 private static boolean isPackageDisabled(PackageManager pm, String packageName) {
2920 try {
2921 PackageInfo pi = pm.getPackageInfo(packageName, 0);
2922 return !pi.applicationInfo.enabled;
2923 } catch (NameNotFoundException e) {
2924 // Fall through
2925 }
2926 return false;
2927 }
2928
2929 public static boolean isValidPackageComponent(PackageManager pm, ComponentName cn) {
2930 if (cn == null) {
2931 return false;
2932 }
2933 if (isPackageDisabled(pm, cn.getPackageName())) {
2934 return false;
2935 }
2936
2937 try {
2938 // Check the activity
2939 PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0);
2940 return (pm.getActivityInfo(cn, 0) != null);
2941 } catch (NameNotFoundException e) {
2942 return false;
2943 }
2944 }
2945
2946 /**
2947 * Make an ShortcutInfo object for a restored application or shortcut item that points
2948 * to a package that is not yet installed on the system.
2949 */
2950 public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent) {
2951 final ShortcutInfo info = new ShortcutInfo();
2952 info.usingFallbackIcon = true;
2953 info.setIcon(getFallbackIcon());
2954 if (cursor != null) {
2955 info.title = cursor.getString(titleIndex);
2956 } else {
2957 info.title = "";
2958 }
2959 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
2960 info.restoredIntent = intent;
2961 return info;
2962 }
2963
2964 /**
2965 * Make an Intent object for a restored application or shortcut item that points
2966 * to the market page for the item.
2967 */
2968 private Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
2969 final boolean debug = false;
2970 ComponentName componentName = intent.getComponent();
2971 Intent marketIntent = new Intent(Intent.ACTION_VIEW);
2972 Uri marketUri = new Uri.Builder()
2973 .scheme("market")
2974 .authority("details")
2975 .appendQueryParameter("id", componentName.getPackageName())
2976 .build();
2977 if (debug) Log.d(TAG, "manufactured intent uri: " + marketUri.toString());
2978 marketIntent.setData(marketUri);
2979 return marketIntent;
2980 }
2981
2982 /**
2983 * This is called from the code that adds shortcuts from the intent receiver. This
2984 * doesn't have a Cursor, but
2985 */
2986 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) {
2987 return getShortcutInfo(manager, intent, context, null, -1, -1, null);
2988 }
2989
2990 /**
2991 * Make an ShortcutInfo object for a shortcut that is an application.
2992 *
2993 * If c is not null, then it will be used to fill in missing data like the title and icon.
2994 */
2995 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context,
2996 Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) {
2997 ComponentName componentName = intent.getComponent();
2998 final ShortcutInfo info = new ShortcutInfo();
2999 if (componentName != null && !isValidPackageComponent(manager, componentName)) {
3000 Log.d(TAG, "Invalid package found in getShortcutInfo: " + componentName);
3001 return null;
3002 } else {
3003 try {
3004 PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0);
3005 info.initFlagsAndFirstInstallTime(pi);
3006 } catch (NameNotFoundException e) {
3007 Log.d(TAG, "getPackInfo failed for package " +
3008 componentName.getPackageName());
3009 }
3010 }
3011
3012 // TODO: See if the PackageManager knows about this case. If it doesn't
3013 // then return null & delete this.
3014
3015 // the resource -- This may implicitly give us back the fallback icon,
3016 // but don't worry about that. All we're doing with usingFallbackIcon is
3017 // to avoid saving lots of copies of that in the database, and most apps
3018 // have icons anyway.
3019
3020 // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and
3021 // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info
3022 // via resolveActivity().
3023 Bitmap icon = null;
3024 ResolveInfo resolveInfo = null;
3025 ComponentName oldComponent = intent.getComponent();
3026 Intent newIntent = new Intent(intent.getAction(), null);
3027 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3028 newIntent.setPackage(oldComponent.getPackageName());
3029 List<ResolveInfo> infos = manager.queryIntentActivities(newIntent, 0);
3030 for (ResolveInfo i : infos) {
3031 ComponentName cn = new ComponentName(i.activityInfo.packageName,
3032 i.activityInfo.name);
3033 if (cn.equals(oldComponent)) {
3034 resolveInfo = i;
3035 }
3036 }
3037 if (resolveInfo == null) {
3038 resolveInfo = manager.resolveActivity(intent, 0);
3039 }
3040 if (resolveInfo != null) {
3041 icon = mIconCache.getIcon(componentName, resolveInfo, labelCache);
3042 }
3043 // the db
3044 if (icon == null) {
3045 if (c != null) {
3046 icon = getIconFromCursor(c, iconIndex, context);
3047 }
3048 }
3049 // the fallback icon
3050 if (icon == null) {
3051 icon = getFallbackIcon();
3052 info.usingFallbackIcon = true;
3053 }
3054 info.setIcon(icon);
3055
3056 // from the resource
3057 if (resolveInfo != null) {
3058 ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo);
3059 if (labelCache != null && labelCache.containsKey(key)) {
3060 info.title = labelCache.get(key);
3061 } else {
3062 info.title = resolveInfo.activityInfo.loadLabel(manager);
3063 if (labelCache != null) {
3064 labelCache.put(key, info.title);
3065 }
3066 }
3067 }
3068 // from the db
3069 if (info.title == null) {
3070 if (c != null) {
3071 info.title = c.getString(titleIndex);
3072 }
3073 }
3074 // fall back to the class name of the activity
3075 if (info.title == null) {
3076 info.title = componentName.getClassName();
3077 }
3078 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3079 return info;
3080 }
3081
3082 static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos,
3083 ItemInfoFilter f) {
3084 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3085 for (ItemInfo i : infos) {
3086 if (i instanceof ShortcutInfo) {
3087 ShortcutInfo info = (ShortcutInfo) i;
3088 ComponentName cn = info.intent.getComponent();
3089 if (cn != null && f.filterItem(null, info, cn)) {
3090 filtered.add(info);
3091 }
3092 } else if (i instanceof FolderInfo) {
3093 FolderInfo info = (FolderInfo) i;
3094 for (ShortcutInfo s : info.contents) {
3095 ComponentName cn = s.intent.getComponent();
3096 if (cn != null && f.filterItem(info, s, cn)) {
3097 filtered.add(s);
3098 }
3099 }
3100 } else if (i instanceof LauncherAppWidgetInfo) {
3101 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3102 ComponentName cn = info.providerName;
3103 if (cn != null && f.filterItem(null, info, cn)) {
3104 filtered.add(info);
3105 }
3106 }
3107 }
3108 return new ArrayList<ItemInfo>(filtered);
3109 }
3110
3111 private ArrayList<ItemInfo> getItemInfoForPackageName(final String pn) {
3112 ItemInfoFilter filter = new ItemInfoFilter() {
3113 @Override
3114 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3115 return cn.getPackageName().equals(pn);
3116 }
3117 };
3118 return filterItemInfos(sBgItemsIdMap.values(), filter);
3119 }
3120
3121 private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname) {
3122 ItemInfoFilter filter = new ItemInfoFilter() {
3123 @Override
3124 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3125 return cn.equals(cname);
3126 }
3127 };
3128 return filterItemInfos(sBgItemsIdMap.values(), filter);
3129 }
3130
3131 public static boolean isShortcutInfoUpdateable(ItemInfo i) {
3132 if (i instanceof ShortcutInfo) {
3133 ShortcutInfo info = (ShortcutInfo) i;
3134 // We need to check for ACTION_MAIN otherwise getComponent() might
3135 // return null for some shortcuts (for instance, for shortcuts to
3136 // web pages.)
3137 Intent intent = info.intent;
3138 ComponentName name = intent.getComponent();
3139 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
3140 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3141 return true;
3142 }
3143 // placeholder shortcuts get special treatment, let them through too.
3144 if (info.getRestoredIntent() != null) {
3145 return true;
3146 }
3147 }
3148 return false;
3149 }
3150
3151 /**
3152 * Make an ShortcutInfo object for a shortcut that isn't an application.
3153 */
3154 private ShortcutInfo getShortcutInfo(Cursor c, Context context,
3155 int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
3156 int titleIndex) {
3157
3158 Bitmap icon = null;
3159 final ShortcutInfo info = new ShortcutInfo();
3160 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3161
3162 // TODO: If there's an explicit component and we can't install that, delete it.
3163
3164 info.title = c.getString(titleIndex);
3165
3166 int iconType = c.getInt(iconTypeIndex);
3167 switch (iconType) {
3168 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
3169 String packageName = c.getString(iconPackageIndex);
3170 String resourceName = c.getString(iconResourceIndex);
3171 PackageManager packageManager = context.getPackageManager();
3172 info.customIcon = false;
3173 // the resource
3174 try {
3175 Resources resources = packageManager.getResourcesForApplication(packageName);
3176 if (resources != null) {
3177 final int id = resources.getIdentifier(resourceName, null, null);
3178 icon = Utilities.createIconBitmap(
3179 mIconCache.getFullResIcon(resources, id), context);
3180 }
3181 } catch (Exception e) {
3182 // drop this. we have other places to look for icons
3183 }
3184 // the db
3185 if (icon == null) {
3186 icon = getIconFromCursor(c, iconIndex, context);
3187 }
3188 // the fallback icon
3189 if (icon == null) {
3190 icon = getFallbackIcon();
3191 info.usingFallbackIcon = true;
3192 }
3193 break;
3194 case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
3195 icon = getIconFromCursor(c, iconIndex, context);
3196 if (icon == null) {
3197 icon = getFallbackIcon();
3198 info.customIcon = false;
3199 info.usingFallbackIcon = true;
3200 } else {
3201 info.customIcon = true;
3202 }
3203 break;
3204 default:
3205 icon = getFallbackIcon();
3206 info.usingFallbackIcon = true;
3207 info.customIcon = false;
3208 break;
3209 }
3210 info.setIcon(icon);
3211 return info;
3212 }
3213
3214 Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) {
3215 @SuppressWarnings("all") // suppress dead code warning
3216 final boolean debug = false;
3217 if (debug) {
3218 Log.d(TAG, "getIconFromCursor app="
3219 + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE)));
3220 }
3221 byte[] data = c.getBlob(iconIndex);
3222 try {
3223 return Utilities.createIconBitmap(
3224 BitmapFactory.decodeByteArray(data, 0, data.length), context);
3225 } catch (Exception e) {
3226 return null;
3227 }
3228 }
3229
3230 ShortcutInfo addShortcut(Context context, Intent data, long container, int screen,
3231 int cellX, int cellY, boolean notify) {
3232 final ShortcutInfo info = infoFromShortcutIntent(context, data, null);
3233 if (info == null) {
3234 return null;
3235 }
3236 addItemToDatabase(context, info, container, screen, cellX, cellY, notify);
3237
3238 return info;
3239 }
3240
3241 /**
3242 * Attempts to find an AppWidgetProviderInfo that matches the given component.
3243 */
3244 AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,
3245 ComponentName component) {
3246 List<AppWidgetProviderInfo> widgets =
3247 AppWidgetManager.getInstance(context).getInstalledProviders();
3248 for (AppWidgetProviderInfo info : widgets) {
3249 if (info.provider.equals(component)) {
3250 return info;
3251 }
3252 }
3253 return null;
3254 }
3255
3256 /**
3257 * Returns a list of all the widgets that can handle configuration with a particular mimeType.
3258 */
3259 List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) {
3260 final PackageManager packageManager = context.getPackageManager();
3261 final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities =
3262 new ArrayList<WidgetMimeTypeHandlerData>();
3263
3264 final Intent supportsIntent =
3265 new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE);
3266 supportsIntent.setType(mimeType);
3267
3268 // Create a set of widget configuration components that we can test against
3269 final List<AppWidgetProviderInfo> widgets =
3270 AppWidgetManager.getInstance(context).getInstalledProviders();
3271 final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget =
3272 new HashMap<ComponentName, AppWidgetProviderInfo>();
3273 for (AppWidgetProviderInfo info : widgets) {
3274 configurationComponentToWidget.put(info.configure, info);
3275 }
3276
3277 // Run through each of the intents that can handle this type of clip data, and cross
3278 // reference them with the components that are actual configuration components
3279 final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent,
3280 PackageManager.MATCH_DEFAULT_ONLY);
3281 for (ResolveInfo info : activities) {
3282 final ActivityInfo activityInfo = info.activityInfo;
3283 final ComponentName infoComponent = new ComponentName(activityInfo.packageName,
3284 activityInfo.name);
3285 if (configurationComponentToWidget.containsKey(infoComponent)) {
3286 supportedConfigurationActivities.add(
3287 new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info,
3288 configurationComponentToWidget.get(infoComponent)));
3289 }
3290 }
3291 return supportedConfigurationActivities;
3292 }
3293
3294 ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) {
3295 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3296 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3297 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3298
3299 if (intent == null) {
3300 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3301 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3302 return null;
3303 }
3304
3305 Bitmap icon = null;
3306 boolean customIcon = false;
3307 ShortcutIconResource iconResource = null;
3308
3309 if (bitmap != null && bitmap instanceof Bitmap) {
3310 icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context);
3311 customIcon = true;
3312 } else {
3313 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3314 if (extra != null && extra instanceof ShortcutIconResource) {
3315 try {
3316 iconResource = (ShortcutIconResource) extra;
3317 final PackageManager packageManager = context.getPackageManager();
3318 Resources resources = packageManager.getResourcesForApplication(
3319 iconResource.packageName);
3320 final int id = resources.getIdentifier(iconResource.resourceName, null, null);
3321 icon = Utilities.createIconBitmap(
3322 mIconCache.getFullResIcon(resources, id), context);
3323 } catch (Exception e) {
3324 Log.w(TAG, "Could not load shortcut icon: " + extra);
3325 }
3326 }
3327 }
3328
3329 final ShortcutInfo info = new ShortcutInfo();
3330
3331 if (icon == null) {
3332 if (fallbackIcon != null) {
3333 icon = fallbackIcon;
3334 } else {
3335 icon = getFallbackIcon();
3336 info.usingFallbackIcon = true;
3337 }
3338 }
3339 info.setIcon(icon);
3340
3341 info.title = name;
3342 info.intent = intent;
3343 info.customIcon = customIcon;
3344 info.iconResource = iconResource;
3345
3346 return info;
3347 }
3348
3349 boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c,
3350 int iconIndex) {
3351 // If apps can't be on SD, don't even bother.
3352 if (!mAppsCanBeOnRemoveableStorage) {
3353 return false;
3354 }
3355 // If this icon doesn't have a custom icon, check to see
3356 // what's stored in the DB, and if it doesn't match what
3357 // we're going to show, store what we are going to show back
3358 // into the DB. We do this so when we're loading, if the
3359 // package manager can't find an icon (for example because
3360 // the app is on SD) then we can use that instead.
3361 if (!info.customIcon && !info.usingFallbackIcon) {
3362 cache.put(info, c.getBlob(iconIndex));
3363 return true;
3364 }
3365 return false;
3366 }
3367 void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) {
3368 boolean needSave = false;
3369 try {
3370 if (data != null) {
3371 Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length);
3372 Bitmap loaded = info.getIcon(mIconCache);
3373 needSave = !saved.sameAs(loaded);
3374 } else {
3375 needSave = true;
3376 }
3377 } catch (Exception e) {
3378 needSave = true;
3379 }
3380 if (needSave) {
3381 Log.d(TAG, "going to save icon bitmap for info=" + info);
3382 // This is slower than is ideal, but this only happens once
3383 // or when the app is updated with a new icon.
3384 updateItemInDatabase(context, info);
3385 }
3386 }
3387
3388 /**
3389 * Return an existing FolderInfo object if we have encountered this ID previously,
3390 * or make a new one.
3391 */
3392 private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
3393 // See if a placeholder was created for us already
3394 FolderInfo folderInfo = folders.get(id);
3395 if (folderInfo == null) {
3396 // No placeholder -- create a new instance
3397 folderInfo = new FolderInfo();
3398 folders.put(id, folderInfo);
3399 }
3400 return folderInfo;
3401 }
3402
3403 public static final Comparator<AppInfo> getAppNameComparator() {
3404 final Collator collator = Collator.getInstance();
3405 return new Comparator<AppInfo>() {
3406 public final int compare(AppInfo a, AppInfo b) {
3407 int result = collator.compare(a.title.toString().trim(),
3408 b.title.toString().trim());
3409 if (result == 0) {
3410 result = a.componentName.compareTo(b.componentName);
3411 }
3412 return result;
3413 }
3414 };
3415 }
3416 public static final Comparator<AppInfo> APP_INSTALL_TIME_COMPARATOR
3417 = new Comparator<AppInfo>() {
3418 public final int compare(AppInfo a, AppInfo b) {
3419 if (a.firstInstallTime < b.firstInstallTime) return 1;
3420 if (a.firstInstallTime > b.firstInstallTime) return -1;
3421 return 0;
3422 }
3423 };
3424 public static final Comparator<AppWidgetProviderInfo> getWidgetNameComparator() {
3425 final Collator collator = Collator.getInstance();
3426 return new Comparator<AppWidgetProviderInfo>() {
3427 public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) {
3428 return collator.compare(a.label.toString().trim(), b.label.toString().trim());
3429 }
3430 };
3431 }
3432 static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) {
3433 if (info.activityInfo != null) {
3434 return new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
3435 } else {
3436 return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
3437 }
3438 }
3439 public static class ShortcutNameComparator implements Comparator<ResolveInfo> {
3440 private Collator mCollator;
3441 private PackageManager mPackageManager;
3442 private HashMap<Object, CharSequence> mLabelCache;
3443 ShortcutNameComparator(PackageManager pm) {
3444 mPackageManager = pm;
3445 mLabelCache = new HashMap<Object, CharSequence>();
3446 mCollator = Collator.getInstance();
3447 }
3448 ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) {
3449 mPackageManager = pm;
3450 mLabelCache = labelCache;
3451 mCollator = Collator.getInstance();
3452 }
3453 public final int compare(ResolveInfo a, ResolveInfo b) {
3454 CharSequence labelA, labelB;
3455 ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a);
3456 ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b);
3457 if (mLabelCache.containsKey(keyA)) {
3458 labelA = mLabelCache.get(keyA);
3459 } else {
3460 labelA = a.loadLabel(mPackageManager).toString().trim();
3461
3462 mLabelCache.put(keyA, labelA);
3463 }
3464 if (mLabelCache.containsKey(keyB)) {
3465 labelB = mLabelCache.get(keyB);
3466 } else {
3467 labelB = b.loadLabel(mPackageManager).toString().trim();
3468
3469 mLabelCache.put(keyB, labelB);
3470 }
3471 return mCollator.compare(labelA, labelB);
3472 }
3473 };
3474 public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
3475 private Collator mCollator;
3476 private PackageManager mPackageManager;
3477 private HashMap<Object, String> mLabelCache;
3478 WidgetAndShortcutNameComparator(PackageManager pm) {
3479 mPackageManager = pm;
3480 mLabelCache = new HashMap<Object, String>();
3481 mCollator = Collator.getInstance();
3482 }
3483 public final int compare(Object a, Object b) {
3484 String labelA, labelB;
3485 if (mLabelCache.containsKey(a)) {
3486 labelA = mLabelCache.get(a);
3487 } else {
3488 labelA = (a instanceof AppWidgetProviderInfo) ?
3489 ((AppWidgetProviderInfo) a).label :
3490 ((ResolveInfo) a).loadLabel(mPackageManager).toString().trim();
3491 mLabelCache.put(a, labelA);
3492 }
3493 if (mLabelCache.containsKey(b)) {
3494 labelB = mLabelCache.get(b);
3495 } else {
3496 labelB = (b instanceof AppWidgetProviderInfo) ?
3497 ((AppWidgetProviderInfo) b).label :
3498 ((ResolveInfo) b).loadLabel(mPackageManager).toString().trim();
3499 mLabelCache.put(b, labelB);
3500 }
3501 return mCollator.compare(labelA, labelB);
3502 }
3503 };
3504
3505 public void dumpState() {
3506 Log.d(TAG, "mCallbacks=" + mCallbacks);
3507 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3508 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3509 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3510 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3511 if (mLoaderTask != null) {
3512 mLoaderTask.dumpState();
3513 } else {
3514 Log.d(TAG, "mLoaderTask=null");
3515 }
3516 }
3517 }
|
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.app.SearchManager;
20 import android.appwidget.AppWidgetManager;
21 import android.appwidget.AppWidgetProviderInfo;
22 import android.content.*;
23 import android.content.Intent.ShortcutIconResource;
24 import android.content.pm.ActivityInfo;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.pm.ResolveInfo;
29 import android.content.res.Configuration;
30 import android.content.res.Resources;
31 import android.database.Cursor;
32 import android.graphics.Bitmap;
33 import android.graphics.BitmapFactory;
34 import android.net.Uri;
35 import android.os.Environment;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.Parcelable;
39 import android.os.Process;
40 import android.os.RemoteException;
41 import android.os.SystemClock;
42 import android.provider.BaseColumns;
43 import android.text.TextUtils;
44 import android.util.Log;
45 import android.util.Pair;
46
47 import com.android.launcher3.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
48
49 import java.lang.ref.WeakReference;
50 import java.net.URISyntaxException;
51 import java.text.Collator;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.Collection;
55 import java.util.Collections;
56 import java.util.Comparator;
57 import java.util.HashMap;
58 import java.util.HashSet;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Set;
62 import java.util.TreeMap;
63 import java.util.concurrent.atomic.AtomicBoolean;
64
65 /**
66 * Maintains in-memory state of the Launcher. It is expected that there should be only one
67 * LauncherModel object held in a static. Also provide APIs for updating the database state
68 * for the Launcher.
69 */
70 public class LauncherModel extends BroadcastReceiver {
71 static final boolean DEBUG_LOADERS = false;
72 static final String TAG = "Launcher.Model";
73
74 // true = use a "More Apps" folder for non-workspace apps on upgrade
75 // false = strew non-workspace apps across the workspace on upgrade
76 public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false;
77
78 public static final int LOADER_FLAG_NONE = 0;
79 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
80 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
81
82 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
83 private static final long INVALID_SCREEN_ID = -1L;
84
85 private final boolean mAppsCanBeOnRemoveableStorage;
86 private final boolean mOldContentProviderExists;
87
88 private final LauncherAppState mApp;
89 private final Object mLock = new Object();
90 private DeferredHandler mHandler = new DeferredHandler();
91 private LoaderTask mLoaderTask;
92 private boolean mIsLoaderTaskRunning;
93 private volatile boolean mFlushingWorkerThread;
94
95 // Specific runnable types that are run on the main thread deferred handler, this allows us to
96 // clear all queued binding runnables when the Launcher activity is destroyed.
97 private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
98 private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
99
100
101 private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
102 static {
103 sWorkerThread.start();
104 }
105 private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
106
107 // We start off with everything not loaded. After that, we assume that
108 // our monitoring of the package manager provides all updates and we never
109 // need to do a requery. These are only ever touched from the loader thread.
110 private boolean mWorkspaceLoaded;
111 private boolean mAllAppsLoaded;
112
113 // When we are loading pages synchronously, we can't just post the binding of items on the side
114 // pages as this delays the rotation process. Instead, we wait for a callback from the first
115 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
116 // a normal load, we also clear this set of Runnables.
117 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
118
119 private WeakReference<Callbacks> mCallbacks;
120
121 // < only access in worker thread >
122 AllAppsList mBgAllAppsList;
123
124 // The lock that must be acquired before referencing any static bg data structures. Unlike
125 // other locks, this one can generally be held long-term because we never expect any of these
126 // static data structures to be referenced outside of the worker thread except on the first
127 // load after configuration change.
128 static final Object sBgLock = new Object();
129
130 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
131 // LauncherModel to their ids
132 static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
133
134 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
135 // created by LauncherModel that are directly on the home screen (however, no widgets or
136 // shortcuts within folders).
137 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
138
139 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
140 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
141 new ArrayList<LauncherAppWidgetInfo>();
142
143 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
144 static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
145
146 // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database
147 static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>();
148
149 // sBgWorkspaceScreens is the ordered set of workspace screens.
150 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
151
152 // </ only access in worker thread >
153
154 private IconCache mIconCache;
155 private Bitmap mDefaultIcon;
156
157 protected int mPreviousConfigMcc;
158
159 public interface Callbacks {
160 public boolean setLoadOnResume();
161 public int getCurrentWorkspaceScreen();
162 public void startBinding();
163 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
164 boolean forceAnimateIcons);
165 public void bindScreens(ArrayList<Long> orderedScreenIds);
166 public void bindAddScreens(ArrayList<Long> orderedScreenIds);
167 public void bindFolders(HashMap<Long,FolderInfo> folders);
168 public void finishBindingItems(boolean upgradePath);
169 public void bindAppWidget(LauncherAppWidgetInfo info);
170 public void bindAllApplications(ArrayList<AppInfo> apps);
171 public void bindAppsAdded(ArrayList<Long> newScreens,
172 ArrayList<ItemInfo> addNotAnimated,
173 ArrayList<ItemInfo> addAnimated,
174 ArrayList<AppInfo> addedApps);
175 public void bindAppsUpdated(ArrayList<AppInfo> apps);
176 public void bindComponentsRemoved(ArrayList<String> packageNames,
177 ArrayList<AppInfo> appInfos);
178 public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
179 public void bindSearchablesChanged();
180 public boolean isAllAppsButtonRank(int rank);
181 public void onPageBoundSynchronously(int page);
182 public void dumpLogsToLocalData();
183 }
184
185 public interface ItemInfoFilter {
186 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
187 }
188
189 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
190 Context context = app.getContext();
191 ContentResolver contentResolver = context.getContentResolver();
192
193 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
194 mOldContentProviderExists = (contentResolver.acquireContentProviderClient(
195 LauncherSettings.Favorites.OLD_CONTENT_URI) != null);
196 mApp = app;
197 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
198 mIconCache = iconCache;
199
200 final Resources res = context.getResources();
201 Configuration config = res.getConfiguration();
202 mPreviousConfigMcc = config.mcc;
203 }
204
205 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
206 * posted on the main thread handler. */
207 private void runOnMainThread(Runnable r) {
208 runOnMainThread(r, 0);
209 }
210 private void runOnMainThread(Runnable r, int type) {
211 if (sWorkerThread.getThreadId() == Process.myTid()) {
212 // If we are on the worker thread, post onto the main handler
213 mHandler.post(r);
214 } else {
215 r.run();
216 }
217 }
218
219 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
220 * posted on the worker thread handler. */
221 private static void runOnWorkerThread(Runnable r) {
222 if (sWorkerThread.getThreadId() == Process.myTid()) {
223 r.run();
224 } else {
225 // If we are not on the worker thread, then post to the worker handler
226 sWorker.post(r);
227 }
228 }
229
230 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
231 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
232 }
233
234 static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> items, int[] xy,
235 long screen) {
236 LauncherAppState app = LauncherAppState.getInstance();
237 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
238 final int xCount = (int) grid.numColumns;
239 final int yCount = (int) grid.numRows;
240 boolean[][] occupied = new boolean[xCount][yCount];
241
242 int cellX, cellY, spanX, spanY;
243 for (int i = 0; i < items.size(); ++i) {
244 final ItemInfo item = items.get(i);
245 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
246 if (item.screenId == screen) {
247 cellX = item.cellX;
248 cellY = item.cellY;
249 spanX = item.spanX;
250 spanY = item.spanY;
251 for (int x = cellX; 0 <= x && x < cellX + spanX && x < xCount; x++) {
252 for (int y = cellY; 0 <= y && y < cellY + spanY && y < yCount; y++) {
253 occupied[x][y] = true;
254 }
255 }
256 }
257 }
258 }
259
260 return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied);
261 }
262 static Pair<Long, int[]> findNextAvailableIconSpace(Context context, String name,
263 Intent launchIntent,
264 int firstScreenIndex,
265 ArrayList<Long> workspaceScreens) {
266 // Lock on the app so that we don't try and get the items while apps are being added
267 LauncherAppState app = LauncherAppState.getInstance();
268 LauncherModel model = app.getModel();
269 boolean found = false;
270 synchronized (app) {
271 if (sWorkerThread.getThreadId() != Process.myTid()) {
272 // Flush the LauncherModel worker thread, so that if we just did another
273 // processInstallShortcut, we give it time for its shortcut to get added to the
274 // database (getItemsInLocalCoordinates reads the database)
275 model.flushWorkerThread();
276 }
277 final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
278
279 // Try adding to the workspace screens incrementally, starting at the default or center
280 // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
281 firstScreenIndex = Math.min(firstScreenIndex, workspaceScreens.size());
282 int count = workspaceScreens.size();
283 for (int screen = firstScreenIndex; screen < count && !found; screen++) {
284 int[] tmpCoordinates = new int[2];
285 if (findNextAvailableIconSpaceInScreen(items, tmpCoordinates,
286 workspaceScreens.get(screen))) {
287 // Update the Launcher db
288 return new Pair<Long, int[]>(workspaceScreens.get(screen), tmpCoordinates);
289 }
290 }
291 }
292 return null;
293 }
294
295 public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
296 final ArrayList<AppInfo> allAppsApps) {
297 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
298 addAndBindAddedApps(context, workspaceApps, cb, allAppsApps);
299 }
300 public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps,
301 final Callbacks callbacks, final ArrayList<AppInfo> allAppsApps) {
302 if (workspaceApps == null || allAppsApps == null) {
303 throw new RuntimeException("workspaceApps and allAppsApps must not be null");
304 }
305 if (workspaceApps.isEmpty() && allAppsApps.isEmpty()) {
306 return;
307 }
308 // Process the newly added applications and add them to the database first
309 Runnable r = new Runnable() {
310 public void run() {
311 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
312 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
313
314 // Get the list of workspace screens. We need to append to this list and
315 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
316 // called.
317 ArrayList<Long> workspaceScreens = new ArrayList<Long>();
318 TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(context);
319 for (Integer i : orderedScreens.keySet()) {
320 long screenId = orderedScreens.get(i);
321 workspaceScreens.add(screenId);
322 }
323
324 synchronized(sBgLock) {
325 Iterator<ItemInfo> iter = workspaceApps.iterator();
326 while (iter.hasNext()) {
327 ItemInfo a = iter.next();
328 final String name = a.title.toString();
329 final Intent launchIntent = a.getIntent();
330
331 // Short-circuit this logic if the icon exists somewhere on the workspace
332 if (LauncherModel.shortcutExists(context, name, launchIntent)) {
333 continue;
334 }
335
336 // Add this icon to the db, creating a new page if necessary. If there
337 // is only the empty page then we just add items to the first page.
338 // Otherwise, we add them to the next pages.
339 int startSearchPageIndex = workspaceScreens.isEmpty() ? 0 : 1;
340 Pair<Long, int[]> coords = LauncherModel.findNextAvailableIconSpace(context,
341 name, launchIntent, startSearchPageIndex, workspaceScreens);
342 if (coords == null) {
343 LauncherProvider lp = LauncherAppState.getLauncherProvider();
344
345 // If we can't find a valid position, then just add a new screen.
346 // This takes time so we need to re-queue the add until the new
347 // page is added. Create as many screens as necessary to satisfy
348 // the startSearchPageIndex.
349 int numPagesToAdd = Math.max(1, startSearchPageIndex + 1 -
350 workspaceScreens.size());
351 while (numPagesToAdd > 0) {
352 long screenId = lp.generateNewScreenId();
353 // Save the screen id for binding in the workspace
354 workspaceScreens.add(screenId);
355 addedWorkspaceScreensFinal.add(screenId);
356 numPagesToAdd--;
357 }
358
359 // Find the coordinate again
360 coords = LauncherModel.findNextAvailableIconSpace(context,
361 name, launchIntent, startSearchPageIndex, workspaceScreens);
362 }
363 if (coords == null) {
364 throw new RuntimeException("Coordinates should not be null");
365 }
366
367 ShortcutInfo shortcutInfo;
368 if (a instanceof ShortcutInfo) {
369 shortcutInfo = (ShortcutInfo) a;
370 } else if (a instanceof AppInfo) {
371 shortcutInfo = ((AppInfo) a).makeShortcut();
372 } else {
373 throw new RuntimeException("Unexpected info type");
374 }
375
376 // Add the shortcut to the db
377 addItemToDatabase(context, shortcutInfo,
378 LauncherSettings.Favorites.CONTAINER_DESKTOP,
379 coords.first, coords.second[0], coords.second[1], false);
380 // Save the ShortcutInfo for binding in the workspace
381 addedShortcutsFinal.add(shortcutInfo);
382 }
383 }
384
385 // Update the workspace screens
386 updateWorkspaceScreenOrder(context, workspaceScreens);
387
388 if (!addedShortcutsFinal.isEmpty() || !allAppsApps.isEmpty()) {
389 runOnMainThread(new Runnable() {
390 public void run() {
391 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
392 if (callbacks == cb && cb != null) {
393 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
394 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
395 if (!addedShortcutsFinal.isEmpty()) {
396 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
397 long lastScreenId = info.screenId;
398 for (ItemInfo i : addedShortcutsFinal) {
399 if (i.screenId == lastScreenId) {
400 addAnimated.add(i);
401 } else {
402 addNotAnimated.add(i);
403 }
404 }
405 }
406 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
407 addNotAnimated, addAnimated, allAppsApps);
408 }
409 }
410 });
411 }
412 }
413 };
414 runOnWorkerThread(r);
415 }
416 <<<<<<< MINE
417 ||||||| BASE
418 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
419 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
420
421 if (allAppsApps == null) {
422 throw new RuntimeException("allAppsApps must not be null");
423 }
424 if (allAppsApps.isEmpty()) {
425 return;
426 }
427
428 // Process the newly added applications and add them to the database first
429 Runnable r = new Runnable() {
430 public void run() {
431 runOnMainThread(new Runnable() {
432 public void run() {
433 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
434 if (callbacks == cb && cb != null) {
435 callbacks.bindAppsAdded(null, null, null, allAppsApps);
436 }
437 }
438 });
439 }
440 };
441 runOnWorkerThread(r);
442 }
443 =======
444 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
445 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
446
447 if (allAppsApps == null) {
448 throw new RuntimeException("allAppsApps must not be null");
449 }
450 if (allAppsApps.isEmpty()) {
451 return;
452 }
453
454 final ArrayList<AppInfo> restoredAppsFinal = new ArrayList<AppInfo>();
455 Iterator<AppInfo> iter = allAppsApps.iterator();
456 while (iter.hasNext()) {
457 ItemInfo a = iter.next();
458 if (LauncherModel.appWasRestored(ctx, a.getIntent())) {
459 restoredAppsFinal.add((AppInfo) a);
460 }
461 }
462
463 // Process the newly added applications and add them to the database first
464 Runnable r = new Runnable() {
465 public void run() {
466 runOnMainThread(new Runnable() {
467 public void run() {
468 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
469 if (callbacks == cb && cb != null) {
470 callbacks.bindAppsAdded(null, null, null, allAppsApps);
471 if (!restoredAppsFinal.isEmpty()) {
472 callbacks.bindAppsUpdated(restoredAppsFinal);
473 }
474 }
475 }
476 });
477 }
478 };
479 runOnWorkerThread(r);
480 }
481 >>>>>>> YOURS
482 <<<<<<< MINE
483 ||||||| BASE
484 public void addAndBindAddedWorkspaceApps(final Context context,
485 final ArrayList<ItemInfo> workspaceApps) {
486 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
487
488 if (workspaceApps == null) {
489 throw new RuntimeException("workspaceApps and allAppsApps must not be null");
490 }
491 if (workspaceApps.isEmpty()) {
492 return;
493 }
494 // Process the newly added applications and add them to the database first
495 Runnable r = new Runnable() {
496 public void run() {
497 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
498 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
499
500 // Get the list of workspace screens. We need to append to this list and
501 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
502 // called.
503 ArrayList<Long> workspaceScreens = new ArrayList<Long>();
504 TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(context);
505 for (Integer i : orderedScreens.keySet()) {
506 long screenId = orderedScreens.get(i);
507 workspaceScreens.add(screenId);
508 }
509
510 synchronized(sBgLock) {
511 Iterator<ItemInfo> iter = workspaceApps.iterator();
512 while (iter.hasNext()) {
513 ItemInfo a = iter.next();
514 final String name = a.title.toString();
515 final Intent launchIntent = a.getIntent();
516
517 // Short-circuit this logic if the icon exists somewhere on the workspace
518 if (LauncherModel.shortcutExists(context, name, launchIntent)) {
519 continue;
520 }
521
522 // Add this icon to the db, creating a new page if necessary. If there
523 // is only the empty page then we just add items to the first page.
524 // Otherwise, we add them to the next pages.
525 int startSearchPageIndex = workspaceScreens.isEmpty() ? 0 : 1;
526 Pair<Long, int[]> coords = LauncherModel.findNextAvailableIconSpace(context,
527 name, launchIntent, startSearchPageIndex, workspaceScreens);
528 if (coords == null) {
529 LauncherProvider lp = LauncherAppState.getLauncherProvider();
530
531 // If we can't find a valid position, then just add a new screen.
532 // This takes time so we need to re-queue the add until the new
533 // page is added. Create as many screens as necessary to satisfy
534 // the startSearchPageIndex.
535 int numPagesToAdd = Math.max(1, startSearchPageIndex + 1 -
536 workspaceScreens.size());
537 while (numPagesToAdd > 0) {
538 long screenId = lp.generateNewScreenId();
539 // Save the screen id for binding in the workspace
540 workspaceScreens.add(screenId);
541 addedWorkspaceScreensFinal.add(screenId);
542 numPagesToAdd--;
543 }
544
545 // Find the coordinate again
546 coords = LauncherModel.findNextAvailableIconSpace(context,
547 name, launchIntent, startSearchPageIndex, workspaceScreens);
548 }
549 if (coords == null) {
550 throw new RuntimeException("Coordinates should not be null");
551 }
552
553 ShortcutInfo shortcutInfo;
554 if (a instanceof ShortcutInfo) {
555 shortcutInfo = (ShortcutInfo) a;
556 } else if (a instanceof AppInfo) {
557 shortcutInfo = ((AppInfo) a).makeShortcut();
558 } else {
559 throw new RuntimeException("Unexpected info type");
560 }
561
562 // Add the shortcut to the db
563 addItemToDatabase(context, shortcutInfo,
564 LauncherSettings.Favorites.CONTAINER_DESKTOP,
565 coords.first, coords.second[0], coords.second[1], false);
566 // Save the ShortcutInfo for binding in the workspace
567 addedShortcutsFinal.add(shortcutInfo);
568 }
569 }
570
571 // Update the workspace screens
572 updateWorkspaceScreenOrder(context, workspaceScreens);
573
574 if (!addedShortcutsFinal.isEmpty()) {
575 runOnMainThread(new Runnable() {
576 public void run() {
577 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
578 if (callbacks == cb && cb != null) {
579 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
580 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
581 if (!addedShortcutsFinal.isEmpty()) {
582 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
583 long lastScreenId = info.screenId;
584 for (ItemInfo i : addedShortcutsFinal) {
585 if (i.screenId == lastScreenId) {
586 addAnimated.add(i);
587 } else {
588 addNotAnimated.add(i);
589 }
590 }
591 }
592 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
593 addNotAnimated, addAnimated, null);
594 }
595 }
596 });
597 }
598 }
599 };
600 runOnWorkerThread(r);
601 }
602 =======
603 public void addAndBindAddedWorkspaceApps(final Context context,
604 final ArrayList<ItemInfo> workspaceApps) {
605 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
606
607 if (workspaceApps == null) {
608 throw new RuntimeException("workspaceApps and allAppsApps must not be null");
609 }
610 if (workspaceApps.isEmpty()) {
611 return;
612 }
613 // Process the newly added applications and add them to the database first
614 Runnable r = new Runnable() {
615 public void run() {
616 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
617 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
618 final ArrayList<AppInfo> restoredAppsFinal = new ArrayList<AppInfo>();
619
620 // Get the list of workspace screens. We need to append to this list and
621 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
622 // called.
623 ArrayList<Long> workspaceScreens = new ArrayList<Long>();
624 TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(context);
625 for (Integer i : orderedScreens.keySet()) {
626 long screenId = orderedScreens.get(i);
627 workspaceScreens.add(screenId);
628 }
629
630 synchronized(sBgLock) {
631 Iterator<ItemInfo> iter = workspaceApps.iterator();
632 while (iter.hasNext()) {
633 ItemInfo a = iter.next();
634 final String name = a.title.toString();
635 final Intent launchIntent = a.getIntent();
636
637 // Short-circuit this logic if the icon exists somewhere on the workspace
638 if (LauncherModel.shortcutExists(context, name, launchIntent)) {
639 // Only InstallShortcutReceiver sends us shortcutInfos, ignore them
640 if (a instanceof AppInfo &&
641 LauncherModel.appWasRestored(context, launchIntent)) {
642 restoredAppsFinal.add((AppInfo) a);
643 }
644 continue;
645 }
646
647 // Add this icon to the db, creating a new page if necessary. If there
648 // is only the empty page then we just add items to the first page.
649 // Otherwise, we add them to the next pages.
650 int startSearchPageIndex = workspaceScreens.isEmpty() ? 0 : 1;
651 Pair<Long, int[]> coords = LauncherModel.findNextAvailableIconSpace(context,
652 name, launchIntent, startSearchPageIndex, workspaceScreens);
653 if (coords == null) {
654 LauncherProvider lp = LauncherAppState.getLauncherProvider();
655
656 // If we can't find a valid position, then just add a new screen.
657 // This takes time so we need to re-queue the add until the new
658 // page is added. Create as many screens as necessary to satisfy
659 // the startSearchPageIndex.
660 int numPagesToAdd = Math.max(1, startSearchPageIndex + 1 -
661 workspaceScreens.size());
662 while (numPagesToAdd > 0) {
663 long screenId = lp.generateNewScreenId();
664 // Save the screen id for binding in the workspace
665 workspaceScreens.add(screenId);
666 addedWorkspaceScreensFinal.add(screenId);
667 numPagesToAdd--;
668 }
669
670 // Find the coordinate again
671 coords = LauncherModel.findNextAvailableIconSpace(context,
672 name, launchIntent, startSearchPageIndex, workspaceScreens);
673 }
674 if (coords == null) {
675 throw new RuntimeException("Coordinates should not be null");
676 }
677
678 ShortcutInfo shortcutInfo;
679 if (a instanceof ShortcutInfo) {
680 shortcutInfo = (ShortcutInfo) a;
681 } else if (a instanceof AppInfo) {
682 shortcutInfo = ((AppInfo) a).makeShortcut();
683 } else {
684 throw new RuntimeException("Unexpected info type");
685 }
686
687 // Add the shortcut to the db
688 addItemToDatabase(context, shortcutInfo,
689 LauncherSettings.Favorites.CONTAINER_DESKTOP,
690 coords.first, coords.second[0], coords.second[1], false);
691 // Save the ShortcutInfo for binding in the workspace
692 addedShortcutsFinal.add(shortcutInfo);
693 }
694 }
695
696 // Update the workspace screens
697 updateWorkspaceScreenOrder(context, workspaceScreens);
698
699 if (!addedShortcutsFinal.isEmpty()) {
700 runOnMainThread(new Runnable() {
701 public void run() {
702 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
703 if (callbacks == cb && cb != null) {
704 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
705 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
706 if (!addedShortcutsFinal.isEmpty()) {
707 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
708 long lastScreenId = info.screenId;
709 for (ItemInfo i : addedShortcutsFinal) {
710 if (i.screenId == lastScreenId) {
711 addAnimated.add(i);
712 } else {
713 addNotAnimated.add(i);
714 }
715 }
716 }
717 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
718 addNotAnimated, addAnimated, null);
719 if (!restoredAppsFinal.isEmpty()) {
720 callbacks.bindAppsUpdated(restoredAppsFinal);
721 }
722 }
723 }
724 });
725 }
726 }
727 };
728 runOnWorkerThread(r);
729 }
730 >>>>>>> YOURS
731
732
733 public Bitmap getFallbackIcon() {
734 if (mDefaultIcon == null) {
735 final Context context = LauncherAppState.getInstance().getContext();
736 mDefaultIcon = Utilities.createIconBitmap(
737 mIconCache.getFullResDefaultActivityIcon(), context);
738 }
739 return Bitmap.createBitmap(mDefaultIcon);
740 }
741
742 public void unbindItemInfosAndClearQueuedBindRunnables() {
743 if (sWorkerThread.getThreadId() == Process.myTid()) {
744 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
745 "main thread");
746 }
747
748 // Clear any deferred bind runnables
749 mDeferredBindRunnables.clear();
750 // Remove any queued bind runnables
751 mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
752 // Unbind all the workspace items
753 unbindWorkspaceItemsOnMainThread();
754 }
755
756 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
757 void unbindWorkspaceItemsOnMainThread() {
758 // Ensure that we don't use the same workspace items data structure on the main thread
759 // by making a copy of workspace items first.
760 final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
761 final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>();
762 synchronized (sBgLock) {
763 tmpWorkspaceItems.addAll(sBgWorkspaceItems);
764 tmpAppWidgets.addAll(sBgAppWidgets);
765 }
766 Runnable r = new Runnable() {
767 @Override
768 public void run() {
769 for (ItemInfo item : tmpWorkspaceItems) {
770 item.unbind();
771 }
772 for (ItemInfo item : tmpAppWidgets) {
773 item.unbind();
774 }
775 }
776 };
777 runOnMainThread(r);
778 }
779
780 /**
781 * Adds an item to the DB if it was not created previously, or move it to a new
782 * <container, screen, cellX, cellY>
783 */
784 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
785 long screenId, int cellX, int cellY) {
786 if (item.container == ItemInfo.NO_ID) {
787 // From all apps
788 addItemToDatabase(context, item, container, screenId, cellX, cellY, false);
789 } else {
790 // From somewhere else
791 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
792 }
793 }
794
795 static void checkItemInfoLocked(
796 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
797 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
798 if (modelItem != null && item != modelItem) {
799 // check all the data is consistent
800 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
801 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
802 ShortcutInfo shortcut = (ShortcutInfo) item;
803 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
804 modelShortcut.intent.filterEquals(shortcut.intent) &&
805 modelShortcut.id == shortcut.id &&
806 modelShortcut.itemType == shortcut.itemType &&
807 modelShortcut.container == shortcut.container &&
808 modelShortcut.screenId == shortcut.screenId &&
809 modelShortcut.cellX == shortcut.cellX &&
810 modelShortcut.cellY == shortcut.cellY &&
811 modelShortcut.spanX == shortcut.spanX &&
812 modelShortcut.spanY == shortcut.spanY &&
813 ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
814 (modelShortcut.dropPos != null &&
815 shortcut.dropPos != null &&
816 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
817 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
818 // For all intents and purposes, this is the same object
819 return;
820 }
821 }
822
823 // the modelItem needs to match up perfectly with item if our model is
824 // to be consistent with the database-- for now, just require
825 // modelItem == item or the equality check above
826 String msg = "item: " + ((item != null) ? item.toString() : "null") +
827 "modelItem: " +
828 ((modelItem != null) ? modelItem.toString() : "null") +
829 "Error: ItemInfo passed to checkItemInfo doesn't match original";
830 RuntimeException e = new RuntimeException(msg);
831 if (stackTrace != null) {
832 e.setStackTrace(stackTrace);
833 }
834 throw e;
835 }
836 }
837
838 static void checkItemInfo(final ItemInfo item) {
839 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
840 final long itemId = item.id;
841 Runnable r = new Runnable() {
842 public void run() {
843 synchronized (sBgLock) {
844 checkItemInfoLocked(itemId, item, stackTrace);
845 }
846 }
847 };
848 runOnWorkerThread(r);
849 }
850
851 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
852 final ItemInfo item, final String callingFunction) {
853 final long itemId = item.id;
854 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
855 final ContentResolver cr = context.getContentResolver();
856
857 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
858 Runnable r = new Runnable() {
859 public void run() {
860 cr.update(uri, values, null, null);
861 updateItemArrays(item, itemId, stackTrace);
862 }
863 };
864 runOnWorkerThread(r);
865 }
866
867 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
868 final ArrayList<ItemInfo> items, final String callingFunction) {
869 final ContentResolver cr = context.getContentResolver();
870
871 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
872 Runnable r = new Runnable() {
873 public void run() {
874 ArrayList<ContentProviderOperation> ops =
875 new ArrayList<ContentProviderOperation>();
876 int count = items.size();
877 for (int i = 0; i < count; i++) {
878 ItemInfo item = items.get(i);
879 final long itemId = item.id;
880 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
881 ContentValues values = valuesList.get(i);
882
883 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
884 updateItemArrays(item, itemId, stackTrace);
885
886 }
887 try {
888 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
889 } catch (Exception e) {
890 e.printStackTrace();
891 }
892 }
893 };
894 runOnWorkerThread(r);
895 }
896
897 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
898 // Lock on mBgLock *after* the db operation
899 synchronized (sBgLock) {
900 checkItemInfoLocked(itemId, item, stackTrace);
901
902 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
903 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
904 // Item is in a folder, make sure this folder exists
905 if (!sBgFolders.containsKey(item.container)) {
906 // An items container is being set to a that of an item which is not in
907 // the list of Folders.
908 String msg = "item: " + item + " container being set to: " +
909 item.container + ", not in the list of folders";
910 Log.e(TAG, msg);
911 }
912 }
913
914 // Items are added/removed from the corresponding FolderInfo elsewhere, such
915 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
916 // that are on the desktop, as appropriate
917 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
918 if (modelItem != null &&
919 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
920 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
921 switch (modelItem.itemType) {
922 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
923 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
924 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
925 if (!sBgWorkspaceItems.contains(modelItem)) {
926 sBgWorkspaceItems.add(modelItem);
927 }
928 break;
929 default:
930 break;
931 }
932 } else {
933 sBgWorkspaceItems.remove(modelItem);
934 }
935 }
936 }
937
938 public void flushWorkerThread() {
939 mFlushingWorkerThread = true;
940 Runnable waiter = new Runnable() {
941 public void run() {
942 synchronized (this) {
943 notifyAll();
944 mFlushingWorkerThread = false;
945 }
946 }
947 };
948
949 synchronized(waiter) {
950 runOnWorkerThread(waiter);
951 if (mLoaderTask != null) {
952 synchronized(mLoaderTask) {
953 mLoaderTask.notify();
954 }
955 }
956 boolean success = false;
957 while (!success) {
958 try {
959 waiter.wait();
960 success = true;
961 } catch (InterruptedException e) {
962 }
963 }
964 }
965 }
966
967 /**
968 * Move an item in the DB to a new <container, screen, cellX, cellY>
969 */
970 static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
971 final long screenId, final int cellX, final int cellY) {
972 item.container = container;
973 item.cellX = cellX;
974 item.cellY = cellY;
975
976 // We store hotseat items in canonical form which is this orientation invariant position
977 // in the hotseat
978 if (context instanceof Launcher && screenId < 0 &&
979 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
980 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
981 } else {
982 item.screenId = screenId;
983 }
984
985 final ContentValues values = new ContentValues();
986 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
987 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
988 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
989 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
990
991 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
992 }
993
994 /**
995 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
996 * cellX, cellY have already been updated on the ItemInfos.
997 */
998 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
999 final long container, final int screen) {
1000
1001 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
1002 int count = items.size();
1003
1004 for (int i = 0; i < count; i++) {
1005 ItemInfo item = items.get(i);
1006 item.container = container;
1007
1008 // We store hotseat items in canonical form which is this orientation invariant position
1009 // in the hotseat
1010 if (context instanceof Launcher && screen < 0 &&
1011 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1012 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
1013 item.cellY);
1014 } else {
1015 item.screenId = screen;
1016 }
1017
1018 final ContentValues values = new ContentValues();
1019 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
1020 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
1021 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
1022 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
1023
1024 contentValues.add(values);
1025 }
1026 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
1027 }
1028
1029 /**
1030 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
1031 */
1032 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
1033 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
1034 item.container = container;
1035 item.cellX = cellX;
1036 item.cellY = cellY;
1037 item.spanX = spanX;
1038 item.spanY = spanY;
1039
1040 // We store hotseat items in canonical form which is this orientation invariant position
1041 // in the hotseat
1042 if (context instanceof Launcher && screenId < 0 &&
1043 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1044 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
1045 } else {
1046 item.screenId = screenId;
1047 }
1048
1049 final ContentValues values = new ContentValues();
1050 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
1051 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
1052 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
1053 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
1054 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
1055 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
1056
1057 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
1058 }
1059
1060 /**
1061 * Update an item to the database in a specified container.
1062 */
1063 static void updateItemInDatabase(Context context, final ItemInfo item) {
1064 final ContentValues values = new ContentValues();
1065 item.onAddToDatabase(values);
1066 item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
1067 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
1068 }
1069
1070 /**
1071 * Returns true if the shortcuts already exists in the database.
1072 * we identify a shortcut by its title and intent.
1073 */
1074 static boolean shortcutExists(Context context, String title, Intent intent) {
1075 final ContentResolver cr = context.getContentResolver();
1076 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
1077 new String[] { "title", "intent" }, "title=? and intent=?",
1078 new String[] { title, intent.toUri(0) }, null);
1079 boolean result = false;
1080 try {
1081 result = c.moveToFirst();
1082 } finally {
1083 c.close();
1084 }
1085 return result;
1086 }
1087
1088 /**
1089 * Returns true if the shortcuts already exists in the database.
1090 * we identify a shortcut by the component name of the intent.
1091 */
1092 static boolean appWasRestored(Context context, Intent intent) {
1093 final ContentResolver cr = context.getContentResolver();
1094 final ComponentName component = intent.getComponent();
1095 if (component == null) {
1096 return false;
1097 }
1098 String componentName = component.flattenToString();
1099 final String where = "intent glob \"*component=" + componentName + "*\" and restored = 1";
1100 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
1101 new String[]{"intent", "restored"}, where, null, null);
1102 boolean result = false;
1103 try {
1104 result = c.moveToFirst();
1105 } finally {
1106 c.close();
1107 }
1108 Log.d(TAG, "shortcutWasRestored is " + result + " for " + componentName);
1109 return result;
1110 }
1111
1112 /**
1113 * Returns an ItemInfo array containing all the items in the LauncherModel.
1114 * The ItemInfo.id is not set through this function.
1115 */
1116 static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) {
1117 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
1118 final ContentResolver cr = context.getContentResolver();
1119 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] {
1120 LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER,
1121 LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Fav🔵
1122 LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null);
1123
1124 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1125 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
1126 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1127 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1128 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1129 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
1130 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
1131
1132 try {
1133 while (c.moveToNext()) {
1134 ItemInfo item = new ItemInfo();
1135 item.cellX = c.getInt(cellXIndex);
1136 item.cellY = c.getInt(cellYIndex);
1137 item.spanX = Math.max(1, c.getInt(spanXIndex));
1138 item.spanY = Math.max(1, c.getInt(spanYIndex));
1139 item.container = c.getInt(containerIndex);
1140 item.itemType = c.getInt(itemTypeIndex);
1141 item.screenId = c.getInt(screenIndex);
1142
1143 items.add(item);
1144 }
1145 } catch (Exception e) {
1146 items.clear();
1147 } finally {
1148 c.close();
1149 }
1150
1151 return items;
1152 }
1153
1154 /**
1155 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
1156 */
1157 FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
1158 final ContentResolver cr = context.getContentResolver();
1159 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
1160 "_id=? and (itemType=? or itemType=?)",
1161 new String[] { String.valueOf(id),
1162 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
1163
1164 try {
1165 if (c.moveToFirst()) {
1166 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
1167 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1168 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
1169 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1170 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1171 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1172
1173 FolderInfo folderInfo = null;
1174 switch (c.getInt(itemTypeIndex)) {
1175 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1176 folderInfo = findOrMakeFolder(folderList, id);
1177 break;
1178 }
1179
1180 folderInfo.title = c.getString(titleIndex);
1181 folderInfo.id = id;
1182 folderInfo.container = c.getInt(containerIndex);
1183 folderInfo.screenId = c.getInt(screenIndex);
1184 folderInfo.cellX = c.getInt(cellXIndex);
1185 folderInfo.cellY = c.getInt(cellYIndex);
1186
1187 return folderInfo;
1188 }
1189 } finally {
1190 c.close();
1191 }
1192
1193 return null;
1194 }
1195
1196 /**
1197 * Add an item to the database in a specified container. Sets the container, screen, cellX and
1198 * cellY fields of the item. Also assigns an ID to the item.
1199 */
1200 static void addItemToDatabase(Context context, final ItemInfo item, final long container,
1201 final long screenId, final int cellX, final int cellY, final boolean notify) {
1202 item.container = container;
1203 item.cellX = cellX;
1204 item.cellY = cellY;
1205 // We store hotseat items in canonical form which is this orientation invariant position
1206 // in the hotseat
1207 if (context instanceof Launcher && screenId < 0 &&
1208 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1209 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
1210 } else {
1211 item.screenId = screenId;
1212 }
1213
1214 final ContentValues values = new ContentValues();
1215 final ContentResolver cr = context.getContentResolver();
1216 item.onAddToDatabase(values);
1217
1218 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
1219 values.put(LauncherSettings.Favorites._ID, item.id);
1220 item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
1221
1222 Runnable r = new Runnable() {
1223 public void run() {
1224 cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
1225 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
1226
1227 // Lock on mBgLock *after* the db operation
1228 synchronized (sBgLock) {
1229 checkItemInfoLocked(item.id, item, null);
1230 sBgItemsIdMap.put(item.id, item);
1231 switch (item.itemType) {
1232 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1233 sBgFolders.put(item.id, (FolderInfo) item);
1234 // Fall through
1235 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1236 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1237 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
1238 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1239 sBgWorkspaceItems.add(item);
1240 } else {
1241 if (!sBgFolders.containsKey(item.container)) {
1242 // Adding an item to a folder that doesn't exist.
1243 String msg = "adding item: " + item + " to a folder that " +
1244 " doesn't exist";
1245 Log.e(TAG, msg);
1246 }
1247 }
1248 break;
1249 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1250 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
1251 break;
1252 }
1253 }
1254 }
1255 };
1256 runOnWorkerThread(r);
1257 }
1258
1259 /**
1260 * Creates a new unique child id, for a given cell span across all layouts.
1261 */
1262 static int getCellLayoutChildId(
1263 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
1264 return (((int) container & 0xFF) << 24)
1265 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1266 }
1267
1268 /**
1269 * Removes the specified item from the database
1270 * @param context
1271 * @param item
1272 */
1273 static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1274 final ContentResolver cr = context.getContentResolver();
1275 final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false);
1276
1277 Runnable r = new Runnable() {
1278 public void run() {
1279 cr.delete(uriToDelete, null, null);
1280
1281 // Lock on mBgLock *after* the db operation
1282 synchronized (sBgLock) {
1283 switch (item.itemType) {
1284 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1285 sBgFolders.remove(item.id);
1286 for (ItemInfo info: sBgItemsIdMap.values()) {
1287 if (info.container == item.id) {
1288 // We are deleting a folder which still contains items that
1289 // think they are contained by that folder.
1290 String msg = "deleting a folder (" + item + ") which still " +
1291 "contains items (" + info + ")";
1292 Log.e(TAG, msg);
1293 }
1294 }
1295 sBgWorkspaceItems.remove(item);
1296 break;
1297 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1298 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1299 sBgWorkspaceItems.remove(item);
1300 break;
1301 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1302 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
1303 break;
1304 }
1305 sBgItemsIdMap.remove(item.id);
1306 sBgDbIconCache.remove(item);
1307 }
1308 }
1309 };
1310 runOnWorkerThread(r);
1311 }
1312
1313 /**
1314 * Update the order of the workspace screens in the database. The array list contains
1315 * a list of screen ids in the order that they should appear.
1316 */
1317 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1318 // Log to disk
1319 Launcher.addDumpLog(TAG, "11683562 - updateWorkspaceScreenOrder()", true);
1320 Launcher.addDumpLog(TAG, "11683562 - screens: " + TextUtils.join(", ", screens), true);
1321
1322 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1323 final ContentResolver cr = context.getContentResolver();
1324 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1325
1326 // Remove any negative screen ids -- these aren't persisted
1327 Iterator<Long> iter = screensCopy.iterator();
1328 while (iter.hasNext()) {
1329 long id = iter.next();
1330 if (id < 0) {
1331 iter.remove();
1332 }
1333 }
1334
1335 Runnable r = new Runnable() {
1336 @Override
1337 public void run() {
1338 // Clear the table
1339 cr.delete(uri, null, null);
1340 int count = screensCopy.size();
1341 ContentValues[] values = new ContentValues[count];
1342 for (int i = 0; i < count; i++) {
1343 ContentValues v = new ContentValues();
1344 long screenId = screensCopy.get(i);
1345 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1346 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1347 values[i] = v;
1348 }
1349 cr.bulkInsert(uri, values);
1350
1351 synchronized (sBgLock) {
1352 sBgWorkspaceScreens.clear();
1353 sBgWorkspaceScreens.addAll(screensCopy);
1354 }
1355 }
1356 };
1357 runOnWorkerThread(r);
1358 }
1359
1360 /**
1361 * Remove the contents of the specified folder from the database
1362 */
1363 static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1364 final ContentResolver cr = context.getContentResolver();
1365
1366 Runnable r = new Runnable() {
1367 public void run() {
1368 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
1369 // Lock on mBgLock *after* the db operation
1370 synchronized (sBgLock) {
1371 sBgItemsIdMap.remove(info.id);
1372 sBgFolders.remove(info.id);
1373 sBgDbIconCache.remove(info);
1374 sBgWorkspaceItems.remove(info);
1375 }
1376
1377 cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
1378 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1379 // Lock on mBgLock *after* the db operation
1380 synchronized (sBgLock) {
1381 for (ItemInfo childInfo : info.contents) {
1382 sBgItemsIdMap.remove(childInfo.id);
1383 sBgDbIconCache.remove(childInfo);
1384 }
1385 }
1386 }
1387 };
1388 runOnWorkerThread(r);
1389 }
1390
1391 /**
1392 * Set this as the current Launcher activity object for the loader.
1393 */
1394 public void initialize(Callbacks callbacks) {
1395 synchronized (mLock) {
1396 mCallbacks = new WeakReference<Callbacks>(callbacks);
1397 }
1398 }
1399
1400 /**
1401 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1402 * ACTION_PACKAGE_CHANGED.
1403 */
1404 @Override
1405 public void onReceive(Context context, Intent intent) {
1406 if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
1407
1408 final String action = intent.getAction();
1409
1410 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
1411 || Intent.ACTION_PACKAGE_REMOVED.equals(action)
1412 || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
1413 final String packageName = intent.getData().getSchemeSpecificPart();
1414 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1415
1416 int op = PackageUpdatedTask.OP_NONE;
1417
1418 if (packageName == null || packageName.length() == 0) {
1419 // they sent us a bad intent
1420 return;
1421 }
1422
1423 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
1424 op = PackageUpdatedTask.OP_UPDATE;
1425 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
1426 if (!replacing) {
1427 op = PackageUpdatedTask.OP_REMOVE;
1428 }
1429 // else, we are replacing the package, so a PACKAGE_ADDED will be sent
1430 // later, we will update the package at this time
1431 } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
1432 if (!replacing) {
1433 op = PackageUpdatedTask.OP_ADD;
1434 } else {
1435 op = PackageUpdatedTask.OP_UPDATE;
1436 }
1437 }
1438
1439 if (op != PackageUpdatedTask.OP_NONE) {
1440 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName }));
1441 }
1442
1443 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
1444 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1445 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1446 if (!replacing) {
1447 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
1448 if (mAppsCanBeOnRemoveableStorage) {
1449 // Only rebind if we support removable storage. It catches the case where
1450 // apps on the external sd card need to be reloaded
1451 startLoaderFromBackground();
1452 }
1453 } else {
1454 // If we are replacing then just update the packages in the list
1455 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1456 packages));
1457 }
1458 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
1459 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1460 if (!replacing) {
1461 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1462 enqueuePackageUpdated(new PackageUpdatedTask(
1463 PackageUpdatedTask.OP_UNAVAILABLE, packages));
1464 }
1465 // else, we are replacing the packages, so ignore this event and wait for
1466 // EXTERNAL_APPLICATIONS_AVAILABLE to update the packages at that time
1467 } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1468 // If we have changed locale we need to clear out the labels in all apps/workspace.
1469 forceReload();
1470 } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
1471 // Check if configuration change was an mcc/mnc change which would affect app resources
1472 // and we would need to clear out the labels in all apps/workspace. Same handling as
1473 // above for ACTION_LOCALE_CHANGED
1474 Configuration currentConfig = context.getResources().getConfiguration();
1475 if (mPreviousConfigMcc != currentConfig.mcc) {
1476 Log.d(TAG, "Reload apps on config change. curr_mcc:"
1477 + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc);
1478 forceReload();
1479 }
1480 // Update previousConfig
1481 mPreviousConfigMcc = currentConfig.mcc;
1482 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
1483 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
1484 if (mCallbacks != null) {
1485 Callbacks callbacks = mCallbacks.get();
1486 if (callbacks != null) {
1487 callbacks.bindSearchablesChanged();
1488 }
1489 }
1490 }
1491 }
1492
1493 private void forceReload() {
1494 resetLoadedState(true, true);
1495
1496 // Do this here because if the launcher activity is running it will be restarted.
1497 // If it's not running startLoaderFromBackground will merely tell it that it needs
1498 // to reload.
1499 startLoaderFromBackground();
1500 }
1501
1502 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1503 synchronized (mLock) {
1504 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1505 // mWorkspaceLoaded to true later
1506 stopLoaderLocked();
1507 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1508 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1509 }
1510 }
1511
1512 /**
1513 * When the launcher is in the background, it's possible for it to miss paired
1514 * configuration changes. So whenever we trigger the loader from the background
1515 * tell the launcher that it needs to re-run the loader when it comes back instead
1516 * of doing it now.
1517 */
1518 public void startLoaderFromBackground() {
1519 boolean runLoader = false;
1520 if (mCallbacks != null) {
1521 Callbacks callbacks = mCallbacks.get();
1522 if (callbacks != null) {
1523 // Only actually run the loader if they're not paused.
1524 if (!callbacks.setLoadOnResume()) {
1525 runLoader = true;
1526 }
1527 }
1528 }
1529 if (runLoader) {
1530 startLoader(false, PagedView.INVALID_RESTORE_PAGE);
1531 }
1532 }
1533
1534 // If there is already a loader task running, tell it to stop.
1535 // returns true if isLaunching() was true on the old task
1536 private boolean stopLoaderLocked() {
1537 boolean isLaunching = false;
1538 LoaderTask oldTask = mLoaderTask;
1539 if (oldTask != null) {
1540 if (oldTask.isLaunching()) {
1541 isLaunching = true;
1542 }
1543 oldTask.stopLocked();
1544 }
1545 return isLaunching;
1546 }
1547
1548 public void startLoader(boolean isLaunching, int synchronousBindPage) {
1549 startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE);
1550 }
1551
1552 public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) {
1553 synchronized (mLock) {
1554 if (DEBUG_LOADERS) {
1555 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
1556 }
1557
1558 // Clear any deferred bind-runnables from the synchronized load process
1559 // We must do this before any loading/binding is scheduled below.
1560 mDeferredBindRunnables.clear();
1561
1562 // Don't bother to start the thread if we know it's not going to do anything
1563 if (mCallbacks != null && mCallbacks.get() != null) {
1564 // If there is already one running, tell it to stop.
1565 // also, don't downgrade isLaunching if we're already running
1566 isLaunching = isLaunching || stopLoaderLocked();
1567 mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags);
1568 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
1569 && mAllAppsLoaded && mWorkspaceLoaded) {
1570 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1571 } else {
1572 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1573 sWorker.post(mLoaderTask);
1574 }
1575 }
1576 }
1577 }
1578
1579 void bindRemainingSynchronousPages() {
1580 // Post the remaining side pages to be loaded
1581 if (!mDeferredBindRunnables.isEmpty()) {
1582 for (final Runnable r : mDeferredBindRunnables) {
1583 mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
1584 }
1585 mDeferredBindRunnables.clear();
1586 }
1587 }
1588
1589 public void stopLoader() {
1590 synchronized (mLock) {
1591 if (mLoaderTask != null) {
1592 mLoaderTask.stopLocked();
1593 }
1594 }
1595 }
1596
1597 /** Loads the workspace screens db into a map of Rank -> ScreenId */
1598 private static TreeMap<Integer, Long> loadWorkspaceScreensDb(Context context) {
1599 final ContentResolver contentResolver = context.getContentResolver();
1600 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1601 final Cursor sc = contentResolver.query(screensUri, null, null, null, null);
1602 TreeMap<Integer, Long> orderedScreens = new TreeMap<Integer, Long>();
1603
1604 try {
1605 final int idIndex = sc.getColumnIndexOrThrow(
1606 LauncherSettings.WorkspaceScreens._ID);
1607 final int rankIndex = sc.getColumnIndexOrThrow(
1608 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
1609 while (sc.moveToNext()) {
1610 try {
1611 long screenId = sc.getLong(idIndex);
1612 int rank = sc.getInt(rankIndex);
1613 orderedScreens.put(rank, screenId);
1614 } catch (Exception e) {
1615 Launcher.addDumpLog(TAG, "Desktop items loading interrupted - invalid screens: " + e,🔵
1616 }
1617 }
1618 } finally {
1619 sc.close();
1620 }
1621
1622 // Log to disk
1623 Launcher.addDumpLog(TAG, "11683562 - loadWorkspaceScreensDb()", true);
1624 ArrayList<String> orderedScreensPairs= new ArrayList<String>();
1625 for (Integer i : orderedScreens.keySet()) {
1626 orderedScreensPairs.add("{ " + i + ": " + orderedScreens.get(i) + " }");
1627 }
1628 Launcher.addDumpLog(TAG, "11683562 - screens: " +
1629 TextUtils.join(", ", orderedScreensPairs), true);
1630 return orderedScreens;
1631 }
1632
1633 public boolean isAllAppsLoaded() {
1634 return mAllAppsLoaded;
1635 }
1636
1637 boolean isLoadingWorkspace() {
1638 synchronized (mLock) {
1639 if (mLoaderTask != null) {
1640 return mLoaderTask.isLoadingWorkspace();
1641 }
1642 }
1643 return false;
1644 }
1645
1646 /**
1647 * Runnable for the thread that loads the contents of the launcher:
1648 * - workspace icons
1649 * - widgets
1650 * - all apps icons
1651 */
1652 private class LoaderTask implements Runnable {
1653 private Context mContext;
1654 private boolean mIsLaunching;
1655 private boolean mIsLoadingAndBindingWorkspace;
1656 private boolean mStopped;
1657 private boolean mLoadAndBindStepFinished;
1658 private int mFlags;
1659
1660 private HashMap<Object, CharSequence> mLabelCache;
1661
1662 LoaderTask(Context context, boolean isLaunching, int flags) {
1663 mContext = context;
1664 mIsLaunching = isLaunching;
1665 mLabelCache = new HashMap<Object, CharSequence>();
1666 mFlags = flags;
1667 }
1668
1669 boolean isLaunching() {
1670 return mIsLaunching;
1671 }
1672
1673 boolean isLoadingWorkspace() {
1674 return mIsLoadingAndBindingWorkspace;
1675 }
1676
1677 /** Returns whether this is an upgrade path */
1678 private boolean loadAndBindWorkspace() {
1679 mIsLoadingAndBindingWorkspace = true;
1680
1681 // Load the workspace
1682 if (DEBUG_LOADERS) {
1683 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1684 }
1685
1686 boolean isUpgradePath = false;
1687 if (!mWorkspaceLoaded) {
1688 isUpgradePath = loadWorkspace();
1689 synchronized (LoaderTask.this) {
1690 if (mStopped) {
1691 return isUpgradePath;
1692 }
1693 mWorkspaceLoaded = true;
1694 }
1695 }
1696
1697 // Bind the workspace
1698 bindWorkspace(-1, isUpgradePath);
1699 return isUpgradePath;
1700 }
1701
1702 private void waitForIdle() {
1703 // Wait until the either we're stopped or the other threads are done.
1704 // This way we don't start loading all apps until the workspace has settled
1705 // down.
1706 synchronized (LoaderTask.this) {
1707 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1708
1709 mHandler.postIdle(new Runnable() {
1710 public void run() {
1711 synchronized (LoaderTask.this) {
1712 mLoadAndBindStepFinished = true;
1713 if (DEBUG_LOADERS) {
1714 Log.d(TAG, "done with previous binding step");
1715 }
1716 LoaderTask.this.notify();
1717 }
1718 }
1719 });
1720
1721 while (!mStopped && !mLoadAndBindStepFinished && !mFlushingWorkerThread) {
1722 try {
1723 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1724 // wait no longer than 1sec at a time
1725 this.wait(1000);
1726 } catch (InterruptedException ex) {
1727 // Ignore
1728 }
1729 }
1730 if (DEBUG_LOADERS) {
1731 Log.d(TAG, "waited "
1732 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1733 + "ms for previous step to finish binding");
1734 }
1735 }
1736 }
1737
1738 void runBindSynchronousPage(int synchronousBindPage) {
1739 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1740 // Ensure that we have a valid page index to load synchronously
1741 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1742 "valid page index");
1743 }
1744 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1745 // Ensure that we don't try and bind a specified page when the pages have not been
1746 // loaded already (we should load everything asynchronously in that case)
1747 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1748 }
1749 synchronized (mLock) {
1750 if (mIsLoaderTaskRunning) {
1751 // Ensure that we are never running the background loading at this point since
1752 // we also touch the background collections
1753 throw new RuntimeException("Error! Background loading is already running");
1754 }
1755 }
1756
1757 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1758 // data structures, we can't allow any other thread to touch that data, but because
1759 // this call is synchronous, we can get away with not locking).
1760
1761 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1762 // operations from the previous activity. We need to ensure that all queued operations
1763 // are executed before any synchronous binding work is done.
1764 mHandler.flush();
1765
1766 // Divide the set of loaded items into those that we are binding synchronously, and
1767 // everything else that is to be bound normally (asynchronously).
1768 bindWorkspace(synchronousBindPage, false);
1769 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1770 // arise from that.
1771 onlyBindAllApps();
1772 }
1773
1774 public void run() {
1775 boolean isUpgrade = false;
1776
1777 synchronized (mLock) {
1778 mIsLoaderTaskRunning = true;
1779 }
1780 // Optimize for end-user experience: if the Launcher is up and // running with the
1781 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1782 // workspace first (default).
1783 keep_running: {
1784 // Elevate priority when Home launches for the first time to avoid
1785 // starving at boot time. Staring at a blank home is not cool.
1786 synchronized (mLock) {
1787 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
1788 (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
1789 android.os.Process.setThreadPriority(mIsLaunching
1790 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
1791 }
1792 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1793 isUpgrade = loadAndBindWorkspace();
1794
1795 if (mStopped) {
1796 break keep_running;
1797 }
1798
1799 // Whew! Hard work done. Slow us down, and wait until the UI thread has
1800 // settled down.
1801 synchronized (mLock) {
1802 if (mIsLaunching) {
1803 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
1804 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
1805 }
1806 }
1807 waitForIdle();
1808
1809 // second step
1810 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1811 loadAndBindAllApps();
1812
1813 // Restore the default thread priority after we are done loading items
1814 synchronized (mLock) {
1815 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
1816 }
1817 }
1818
1819 // Update the saved icons if necessary
1820 if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
1821 synchronized (sBgLock) {
1822 for (Object key : sBgDbIconCache.keySet()) {
1823 updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
1824 }
1825 sBgDbIconCache.clear();
1826 }
1827
1828 if (LauncherAppState.isDisableAllApps()) {
1829 // Ensure that all the applications that are in the system are
1830 // represented on the home screen.
1831 if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) {
1832 verifyApplications();
1833 }
1834 }
1835
1836 // Clear out this reference, otherwise we end up holding it until all of the
1837 // callback runnables are done.
1838 mContext = null;
1839
1840 synchronized (mLock) {
1841 // If we are still the last one to be scheduled, remove ourselves.
1842 if (mLoaderTask == this) {
1843 mLoaderTask = null;
1844 }
1845 mIsLoaderTaskRunning = false;
1846 }
1847 }
1848
1849 public void stopLocked() {
1850 synchronized (LoaderTask.this) {
1851 mStopped = true;
1852 this.notify();
1853 }
1854 }
1855
1856 /**
1857 * Gets the callbacks object. If we've been stopped, or if the launcher object
1858 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1859 * object that was around when the deferred message was scheduled, and if there's
1860 * a new Callbacks object around then also return null. This will save us from
1861 * calling onto it with data that will be ignored.
1862 */
1863 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1864 synchronized (mLock) {
1865 if (mStopped) {
1866 return null;
1867 }
1868
1869 if (mCallbacks == null) {
1870 return null;
1871 }
1872
1873 final Callbacks callbacks = mCallbacks.get();
1874 if (callbacks != oldCallbacks) {
1875 return null;
1876 }
1877 if (callbacks == null) {
1878 Log.w(TAG, "no mCallbacks");
1879 return null;
1880 }
1881
1882 return callbacks;
1883 }
1884 }
1885
1886 private void verifyApplications() {
1887 final Context context = mApp.getContext();
1888
1889 // Cross reference all the applications in our apps list with items in the workspace
1890 ArrayList<ItemInfo> tmpInfos;
1891 ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
1892 synchronized (sBgLock) {
1893 for (AppInfo app : mBgAllAppsList.data) {
1894 tmpInfos = getItemInfoForComponentName(app.componentName);
1895 if (tmpInfos.isEmpty()) {
1896 // We are missing an application icon, so add this to the workspace
1897 added.add(app);
1898 // This is a rare event, so lets log it
1899 Log.e(TAG, "Missing Application on load: " + app);
1900 }
1901 }
1902 }
1903 if (!added.isEmpty()) {
1904 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
1905 addAndBindAddedApps(context, added, cb, new ArrayList<AppInfo>());
1906 }
1907 }
1908
1909 // check & update map of what's occupied; used to discard overlapping/invalid items
1910 private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item,
1911 AtomicBoolean deleteOnInvalidPlacement) {
1912 LauncherAppState app = LauncherAppState.getInstance();
1913 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1914 final int countX = (int) grid.numColumns;
1915 final int countY = (int) grid.numRows;
1916
1917 long containerIndex = item.screenId;
1918 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1919 // Return early if we detect that an item is under the hotseat button
1920 if (mCallbacks == null ||
1921 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
1922 deleteOnInvalidPlacement.set(true);
1923 Log.e(TAG, "Error loading shortcut into hotseat " + item
1924 + " into position (" + item.screenId + ":" + item.cellX + ","
1925 + item.cellY + ") occupied by all apps");
1926 return false;
1927 }
1928
1929 final ItemInfo[][] hotseatItems =
1930 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1931
1932 if (item.screenId >= grid.numHotseatIcons) {
1933 Log.e(TAG, "Error loading shortcut " + item
1934 + " into hotseat position " + item.screenId
1935 + ", position out of bounds: (0 to " + (grid.numHotseatIcons - 1)
1936 + ")");
1937 return false;
1938 }
1939
1940 if (hotseatItems != null) {
1941 if (hotseatItems[(int) item.screenId][0] != null) {
1942 Log.e(TAG, "Error loading shortcut into hotseat " + item
1943 + " into position (" + item.screenId + ":" + item.cellX + ","
1944 + item.cellY + ") occupied by "
1945 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
1946 [(int) item.screenId][0]);
1947 return false;
1948 } else {
1949 hotseatItems[(int) item.screenId][0] = item;
1950 return true;
1951 }
1952 } else {
1953 final ItemInfo[][] items = new ItemInfo[(int) grid.numHotseatIcons][1];
1954 items[(int) item.screenId][0] = item;
1955 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
1956 return true;
1957 }
1958 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1959 // Skip further checking if it is not the hotseat or workspace container
1960 return true;
1961 }
1962
1963 if (!occupied.containsKey(item.screenId)) {
1964 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1965 occupied.put(item.screenId, items);
1966 }
1967
1968 final ItemInfo[][] screens = occupied.get(item.screenId);
1969 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1970 item.cellX < 0 || item.cellY < 0 ||
1971 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1972 Log.e(TAG, "Error loading shortcut " + item
1973 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1974 + item.cellX + "," + item.cellY
1975 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1976 return false;
1977 }
1978
1979 // Check if any workspace icons overlap with each other
1980 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1981 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1982 if (screens[x][y] != null) {
1983 Log.e(TAG, "Error loading shortcut " + item
1984 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1985 + x + "," + y
1986 + ") occupied by "
1987 + screens[x][y]);
1988 return false;
1989 }
1990 }
1991 }
1992 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1993 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1994 screens[x][y] = item;
1995 }
1996 }
1997
1998 return true;
1999 }
2000
2001 /** Clears all the sBg data structures */
2002 private void clearSBgDataStructures() {
2003 synchronized (sBgLock) {
2004 sBgWorkspaceItems.clear();
2005 sBgAppWidgets.clear();
2006 sBgFolders.clear();
2007 sBgItemsIdMap.clear();
2008 sBgDbIconCache.clear();
2009 sBgWorkspaceScreens.clear();
2010 }
2011 }
2012
2013 /** Returns whether this is an upgrade path */
2014 private boolean loadWorkspace() {
2015 // Log to disk
2016 Launcher.addDumpLog(TAG, "11683562 - loadWorkspace()", true);
2017
2018 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2019
2020 final Context context = mContext;
2021 final ContentResolver contentResolver = context.getContentResolver();
2022 final PackageManager manager = context.getPackageManager();
2023 final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
2024 final boolean isSafeMode = manager.isSafeMode();
2025
2026 LauncherAppState app = LauncherAppState.getInstance();
2027 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2028 int countX = (int) grid.numColumns;
2029 int countY = (int) grid.numRows;
2030
2031 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
2032 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
2033 LauncherAppState.getLauncherProvider().deleteDatabase();
2034 }
2035
2036 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
2037 // append the user's Launcher2 shortcuts
2038 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
2039 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
2040 } else {
2041 // Make sure the default workspace is loaded
2042 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
2043 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
2044 }
2045
2046 // Check if we need to do any upgrade-path logic
2047 // (Includes having just imported default favorites)
2048 boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb();
2049
2050 // Log to disk
2051 Launcher.addDumpLog(TAG, "11683562 - loadedOldDb: " + loadedOldDb, true);
2052
2053 synchronized (sBgLock) {
2054 clearSBgDataStructures();
2055
2056 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
2057 final ArrayList<Long> restoredRows = new ArrayList<Long>();
2058 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
2059 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
2060 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
2061
2062 // +1 for the hotseat (it can be larger than the workspace)
2063 // Load workspace in reverse order to ensure that latest items are loaded first (and
2064 // before any earlier duplicates)
2065 final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>();
2066
2067 try {
2068 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
2069 final int intentIndex = c.getColumnIndexOrThrow
2070 (LauncherSettings.Favorites.INTENT);
2071 final int titleIndex = c.getColumnIndexOrThrow
2072 (LauncherSettings.Favorites.TITLE);
2073 final int iconTypeIndex = c.getColumnIndexOrThrow(
2074 LauncherSettings.Favorites.ICON_TYPE);
2075 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
2076 final int iconPackageIndex = c.getColumnIndexOrThrow(
2077 LauncherSettings.Favorites.ICON_PACKAGE);
2078 final int iconResourceIndex = c.getColumnIndexOrThrow(
2079 LauncherSettings.Favorites.ICON_RESOURCE);
2080 final int containerIndex = c.getColumnIndexOrThrow(
2081 LauncherSettings.Favorites.CONTAINER);
2082 final int itemTypeIndex = c.getColumnIndexOrThrow(
2083 LauncherSettings.Favorites.ITEM_TYPE);
2084 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
2085 LauncherSettings.Favorites.APPWIDGET_ID);
2086 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
2087 LauncherSettings.Favorites.APPWIDGET_PROVIDER);
2088 final int screenIndex = c.getColumnIndexOrThrow(
2089 LauncherSettings.Favorites.SCREEN);
2090 final int cellXIndex = c.getColumnIndexOrThrow
2091 (LauncherSettings.Favorites.CELLX);
2092 final int cellYIndex = c.getColumnIndexOrThrow
2093 (LauncherSettings.Favorites.CELLY);
2094 final int spanXIndex = c.getColumnIndexOrThrow
2095 (LauncherSettings.Favorites.SPANX);
2096 final int spanYIndex = c.getColumnIndexOrThrow(
2097 LauncherSettings.Favorites.SPANY);
2098 final int restoredIndex = c.getColumnIndexOrThrow(
2099 LauncherSettings.Favorites.RESTORED);
2100 //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
2101 //final int displayModeIndex = c.getColumnIndexOrThrow(
2102 // LauncherSettings.Favorites.DISPLAY_MODE);
2103
2104 ShortcutInfo info;
2105 String intentDescription;
2106 LauncherAppWidgetInfo appWidgetInfo;
2107 int container;
2108 long id;
2109 Intent intent;
2110
2111 while (!mStopped && c.moveToNext()) {
2112 AtomicBoolean deleteOnInvalidPlacement = new AtomicBoolean(false);
2113 try {
2114 int itemType = c.getInt(itemTypeIndex);
2115 boolean restored = 0 != c.getInt(restoredIndex);
2116
2117 switch (itemType) {
2118 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
2119 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2120 id = c.getLong(idIndex);
2121 intentDescription = c.getString(intentIndex);
2122 try {
2123 intent = Intent.parseUri(intentDescription, 0);
2124 ComponentName cn = intent.getComponent();
2125 if (cn != null && !isValidPackageComponent(manager, cn)) {
2126 if (restored) {
2127 // might be installed later
2128 Launcher.addDumpLog(TAG,
2129 "package not yet restored: " + cn, true);
2130 } else {
2131 if (!mAppsCanBeOnRemoveableStorage) {
2132 // Log the invalid package, and remove it
2133 Launcher.addDumpLog(TAG,
2134 "Invalid package removed: " + cn, true);
2135 itemsToRemove.add(id);
2136 } else {
2137 // If apps can be on external storage, then we just
2138 // leave them for the user to remove (maybe add
2139 // visual treatment to it)
2140 Launcher.addDumpLog(TAG,
2141 "Invalid package found: " + cn, true);
2142 }
2143 continue;
2144 }
2145 } else if (restored) {
2146 // no special handling necessary for this restored item
2147 restoredRows.add(id);
2148 restored = false;
2149 }
2150 } catch (URISyntaxException e) {
2151 Launcher.addDumpLog(TAG,
2152 "Invalid uri: " + intentDescription, true);
2153 continue;
2154 }
2155
2156 if (restored) {
2157 Launcher.addDumpLog(TAG,
2158 "constructing info for partially restored package",
2159 true);
2160 info = getRestoredItemInfo(c, titleIndex, intent);
2161 intent = getRestoredItemIntent(c, context, intent);
2162 } else if (itemType ==
2163 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2164 info = getShortcutInfo(manager, intent, context, c, iconIndex,
2165 titleIndex, mLabelCache);
2166 } else {
2167 info = getShortcutInfo(c, context, iconTypeIndex,
2168 iconPackageIndex, iconResourceIndex, iconIndex,
2169 titleIndex);
2170
2171 // App shortcuts that used to be automatically added to Launcher
2172 // didn't always have the correct intent flags set, so do that
2173 // here
2174 if (intent.getAction() != null &&
2175 intent.getCategories() != null &&
2176 intent.getAction().equals(Intent.ACTION_MAIN) &&
2177 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
2178 intent.addFlags(
2179 Intent.FLAG_ACTIVITY_NEW_TASK |
2180 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
2181 }
2182 }
2183
2184 if (info != null) {
2185 info.id = id;
2186 info.intent = intent;
2187 container = c.getInt(containerIndex);
2188 info.container = container;
2189 info.screenId = c.getInt(screenIndex);
2190 info.cellX = c.getInt(cellXIndex);
2191 info.cellY = c.getInt(cellYIndex);
2192 info.spanX = 1;
2193 info.spanY = 1;
2194
2195 // check & update map of what's occupied
2196 deleteOnInvalidPlacement.set(false);
2197 if (!checkItemPlacement(occupied, info, deleteOnInvalidPlacement)) {
2198 if (deleteOnInvalidPlacement.get()) {
2199 itemsToRemove.add(id);
2200 }
2201 break;
2202 }
2203
2204 switch (container) {
2205 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2206 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2207 sBgWorkspaceItems.add(info);
2208 break;
2209 default:
2210 // Item is in a user folder
2211 FolderInfo folderInfo =
2212 findOrMakeFolder(sBgFolders, container);
2213 folderInfo.add(info);
2214 break;
2215 }
2216 sBgItemsIdMap.put(info.id, info);
2217
2218 // now that we've loaded everthing re-save it with the
2219 // icon in case it disappears somehow.
2220 queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
2221 } else {
2222 throw new RuntimeException("Unexpected null ShortcutInfo");
2223 }
2224 break;
2225
2226 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2227 id = c.getLong(idIndex);
2228 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2229
2230 folderInfo.title = c.getString(titleIndex);
2231 folderInfo.id = id;
2232 container = c.getInt(containerIndex);
2233 folderInfo.container = container;
2234 folderInfo.screenId = c.getInt(screenIndex);
2235 folderInfo.cellX = c.getInt(cellXIndex);
2236 folderInfo.cellY = c.getInt(cellYIndex);
2237 folderInfo.spanX = 1;
2238 folderInfo.spanY = 1;
2239
2240 // check & update map of what's occupied
2241 deleteOnInvalidPlacement.set(false);
2242 if (!checkItemPlacement(occupied, folderInfo,
2243 deleteOnInvalidPlacement)) {
2244 if (deleteOnInvalidPlacement.get()) {
2245 itemsToRemove.add(id);
2246 }
2247 break;
2248 }
2249
2250 switch (container) {
2251 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2252 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2253 sBgWorkspaceItems.add(folderInfo);
2254 break;
2255 }
2256
2257 if (restored) {
2258 // no special handling required for restored folders
2259 restoredRows.add(id);
2260 }
2261
2262 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2263 sBgFolders.put(folderInfo.id, folderInfo);
2264 break;
2265
2266 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2267 // Read all Launcher-specific widget details
2268 int appWidgetId = c.getInt(appWidgetIdIndex);
2269 String savedProvider = c.getString(appWidgetProviderIndex);
2270
2271 id = c.getLong(idIndex);
2272
2273 final AppWidgetProviderInfo provider =
2274 widgets.getAppWidgetInfo(appWidgetId);
2275
2276 if (!isSafeMode && (provider == null || provider.provider == null ||
2277 provider.provider.getPackageName() == null)) {
2278 String log = "Deleting widget that isn't installed anymore: id="
2279 + id + " appWidgetId=" + appWidgetId;
2280 Log.e(TAG, log);
2281 Launcher.addDumpLog(TAG, log, false);
2282 itemsToRemove.add(id);
2283 } else {
2284 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2285 provider.provider);
2286 appWidgetInfo.id = id;
2287 appWidgetInfo.screenId = c.getInt(screenIndex);
2288 appWidgetInfo.cellX = c.getInt(cellXIndex);
2289 appWidgetInfo.cellY = c.getInt(cellYIndex);
2290 appWidgetInfo.spanX = c.getInt(spanXIndex);
2291 appWidgetInfo.spanY = c.getInt(spanYIndex);
2292 int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
2293 appWidgetInfo.minSpanX = minSpan[0];
2294 appWidgetInfo.minSpanY = minSpan[1];
2295
2296 container = c.getInt(containerIndex);
2297 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2298 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2299 Log.e(TAG, "Widget found where container != " +
2300 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2301 continue;
2302 }
2303
2304 appWidgetInfo.container = c.getInt(containerIndex);
2305 // check & update map of what's occupied
2306 deleteOnInvalidPlacement.set(false);
2307 if (!checkItemPlacement(occupied, appWidgetInfo,
2308 deleteOnInvalidPlacement)) {
2309 if (deleteOnInvalidPlacement.get()) {
2310 itemsToRemove.add(id);
2311 }
2312 break;
2313 }
2314 String providerName = provider.provider.flattenToString();
2315 if (!providerName.equals(savedProvider)) {
2316 ContentValues values = new ContentValues();
2317 values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2318 providerName);
2319 String where = BaseColumns._ID + "= ?";
2320 String[] args = {Integer.toString(c.getInt(idIndex))};
2321 contentResolver.update(contentUri, values, where, args);
2322 }
2323 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2324 sBgAppWidgets.add(appWidgetInfo);
2325 }
2326 break;
2327 }
2328 } catch (Exception e) {
2329 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2330 }
2331 }
2332 } finally {
2333 if (c != null) {
2334 c.close();
2335 }
2336 }
2337
2338 // Break early if we've stopped loading
2339 if (mStopped) {
2340 clearSBgDataStructures();
2341 return false;
2342 }
2343
2344 if (itemsToRemove.size() > 0) {
2345 ContentProviderClient client = contentResolver.acquireContentProviderClient(
2346 LauncherSettings.Favorites.CONTENT_URI);
2347 // Remove dead items
2348 for (long id : itemsToRemove) {
2349 if (DEBUG_LOADERS) {
2350 Log.d(TAG, "Removed id = " + id);
2351 }
2352 // Don't notify content observers
2353 try {
2354 client.delete(LauncherSettings.Favorites.getContentUri(id, false),
2355 null, null);
2356 } catch (RemoteException e) {
2357 Log.w(TAG, "Could not remove id = " + id);
2358 }
2359 }
2360 }
2361
2362 if (restoredRows.size() > 0) {
2363 ContentProviderClient updater = contentResolver.acquireContentProviderClient(
2364 LauncherSettings.Favorites.CONTENT_URI);
2365 // Update restored items that no longer require special handling
2366 try {
2367 StringBuilder selectionBuilder = new StringBuilder();
2368 selectionBuilder.append(LauncherSettings.Favorites._ID);
2369 selectionBuilder.append(" IN (");
2370 selectionBuilder.append(TextUtils.join(", ", restoredRows));
2371 selectionBuilder.append(")");
2372 ContentValues values = new ContentValues();
2373 values.put(LauncherSettings.Favorites.RESTORED, 0);
2374 updater.update(LauncherSettings.Favorites.CONTENT_URI,
2375 values, selectionBuilder.toString(), null);
2376 } catch (RemoteException e) {
2377 Log.w(TAG, "Could not update restored rows");
2378 }
2379 }
2380
2381 if (loadedOldDb) {
2382 long maxScreenId = 0;
2383 // If we're importing we use the old screen order.
2384 for (ItemInfo item: sBgItemsIdMap.values()) {
2385 long screenId = item.screenId;
2386 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2387 !sBgWorkspaceScreens.contains(screenId)) {
2388 sBgWorkspaceScreens.add(screenId);
2389 if (screenId > maxScreenId) {
2390 maxScreenId = screenId;
2391 }
2392 }
2393 }
2394 Collections.sort(sBgWorkspaceScreens);
2395 // Log to disk
2396 Launcher.addDumpLog(TAG, "11683562 - maxScreenId: " + maxScreenId, true);
2397 Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
2398 TextUtils.join(", ", sBgWorkspaceScreens), true);
2399
2400 LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId);
2401 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2402
2403 // Update the max item id after we load an old db
2404 long maxItemId = 0;
2405 // If we're importing we use the old screen order.
2406 for (ItemInfo item: sBgItemsIdMap.values()) {
2407 maxItemId = Math.max(maxItemId, item.id);
2408 }
2409 LauncherAppState.getLauncherProvider().updateMaxItemId(maxItemId);
2410 } else {
2411 TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(mContext);
2412 for (Integer i : orderedScreens.keySet()) {
2413 sBgWorkspaceScreens.add(orderedScreens.get(i));
2414 }
2415 // Log to disk
2416 Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " +
2417 TextUtils.join(", ", sBgWorkspaceScreens), true);
2418
2419 // Remove any empty screens
2420 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2421 for (ItemInfo item: sBgItemsIdMap.values()) {
2422 long screenId = item.screenId;
2423 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2424 unusedScreens.contains(screenId)) {
2425 unusedScreens.remove(screenId);
2426 }
2427 }
2428
2429 // If there are any empty screens remove them, and update.
2430 if (unusedScreens.size() != 0) {
2431 // Log to disk
2432 Launcher.addDumpLog(TAG, "11683562 - unusedScreens (to be removed): " +
2433 TextUtils.join(", ", unusedScreens), true);
2434
2435 sBgWorkspaceScreens.removeAll(unusedScreens);
2436 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2437 }
2438 }
2439
2440 if (DEBUG_LOADERS) {
2441 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
2442 Log.d(TAG, "workspace layout: ");
2443 int nScreens = occupied.size();
2444 for (int y = 0; y < countY; y++) {
2445 String line = "";
2446
2447 Iterator<Long> iter = occupied.keySet().iterator();
2448 while (iter.hasNext()) {
2449 long screenId = iter.next();
2450 if (screenId > 0) {
2451 line += " | ";
2452 }
2453 for (int x = 0; x < countX; x++) {
2454 line += ((occupied.get(screenId)[x][y] != null) ? "#" : ".");
2455 }
2456 }
2457 Log.d(TAG, "[ " + line + " ]");
2458 }
2459 }
2460 }
2461 return loadedOldDb;
2462 }
2463
2464 /** Filters the set of items who are directly or indirectly (via another container) on the
2465 * specified screen. */
2466 private void filterCurrentWorkspaceItems(long currentScreenId,
2467 ArrayList<ItemInfo> allWorkspaceItems,
2468 ArrayList<ItemInfo> currentScreenItems,
2469 ArrayList<ItemInfo> otherScreenItems) {
2470 // Purge any null ItemInfos
2471 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2472 while (iter.hasNext()) {
2473 ItemInfo i = iter.next();
2474 if (i == null) {
2475 iter.remove();
2476 }
2477 }
2478
2479 // Order the set of items by their containers first, this allows use to walk through the
2480 // list sequentially, build up a list of containers that are in the specified screen,
2481 // as well as all items in those containers.
2482 Set<Long> itemsOnScreen = new HashSet<Long>();
2483 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2484 @Override
2485 public int compare(ItemInfo lhs, ItemInfo rhs) {
2486 return (int) (lhs.container - rhs.container);
2487 }
2488 });
2489 for (ItemInfo info : allWorkspaceItems) {
2490 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2491 if (info.screenId == currentScreenId) {
2492 currentScreenItems.add(info);
2493 itemsOnScreen.add(info.id);
2494 } else {
2495 otherScreenItems.add(info);
2496 }
2497 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2498 currentScreenItems.add(info);
2499 itemsOnScreen.add(info.id);
2500 } else {
2501 if (itemsOnScreen.contains(info.container)) {
2502 currentScreenItems.add(info);
2503 itemsOnScreen.add(info.id);
2504 } else {
2505 otherScreenItems.add(info);
2506 }
2507 }
2508 }
2509 }
2510
2511 /** Filters the set of widgets which are on the specified screen. */
2512 private void filterCurrentAppWidgets(long currentScreenId,
2513 ArrayList<LauncherAppWidgetInfo> appWidgets,
2514 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2515 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2516
2517 for (LauncherAppWidgetInfo widget : appWidgets) {
2518 if (widget == null) continue;
2519 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2520 widget.screenId == currentScreenId) {
2521 currentScreenWidgets.add(widget);
2522 } else {
2523 otherScreenWidgets.add(widget);
2524 }
2525 }
2526 }
2527
2528 /** Filters the set of folders which are on the specified screen. */
2529 private void filterCurrentFolders(long currentScreenId,
2530 HashMap<Long, ItemInfo> itemsIdMap,
2531 HashMap<Long, FolderInfo> folders,
2532 HashMap<Long, FolderInfo> currentScreenFolders,
2533 HashMap<Long, FolderInfo> otherScreenFolders) {
2534
2535 for (long id : folders.keySet()) {
2536 ItemInfo info = itemsIdMap.get(id);
2537 FolderInfo folder = folders.get(id);
2538 if (info == null || folder == null) continue;
2539 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2540 info.screenId == currentScreenId) {
2541 currentScreenFolders.put(id, folder);
2542 } else {
2543 otherScreenFolders.put(id, folder);
2544 }
2545 }
2546 }
2547
2548 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2549 * right) */
2550 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2551 final LauncherAppState app = LauncherAppState.getInstance();
2552 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2553 // XXX: review this
2554 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2555 @Override
2556 public int compare(ItemInfo lhs, ItemInfo rhs) {
2557 int cellCountX = (int) grid.numColumns;
2558 int cellCountY = (int) grid.numRows;
2559 int screenOffset = cellCountX * cellCountY;
2560 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
2561 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
2562 lhs.cellY * cellCountX + lhs.cellX);
2563 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
2564 rhs.cellY * cellCountX + rhs.cellX);
2565 return (int) (lr - rr);
2566 }
2567 });
2568 }
2569
2570 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2571 final ArrayList<Long> orderedScreens) {
2572 final Runnable r = new Runnable() {
2573 @Override
2574 public void run() {
2575 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2576 if (callbacks != null) {
2577 callbacks.bindScreens(orderedScreens);
2578 }
2579 }
2580 };
2581 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2582 }
2583
2584 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2585 final ArrayList<ItemInfo> workspaceItems,
2586 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2587 final HashMap<Long, FolderInfo> folders,
2588 ArrayList<Runnable> deferredBindRunnables) {
2589
2590 final boolean postOnMainThread = (deferredBindRunnables != null);
2591
2592 // Bind the workspace items
2593 int N = workspaceItems.size();
2594 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2595 final int start = i;
2596 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2597 final Runnable r = new Runnable() {
2598 @Override
2599 public void run() {
2600 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2601 if (callbacks != null) {
2602 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2603 false);
2604 }
2605 }
2606 };
2607 if (postOnMainThread) {
2608 deferredBindRunnables.add(r);
2609 } else {
2610 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2611 }
2612 }
2613
2614 // Bind the folders
2615 if (!folders.isEmpty()) {
2616 final Runnable r = new Runnable() {
2617 public void run() {
2618 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2619 if (callbacks != null) {
2620 callbacks.bindFolders(folders);
2621 }
2622 }
2623 };
2624 if (postOnMainThread) {
2625 deferredBindRunnables.add(r);
2626 } else {
2627 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2628 }
2629 }
2630
2631 // Bind the widgets, one at a time
2632 N = appWidgets.size();
2633 for (int i = 0; i < N; i++) {
2634 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2635 final Runnable r = new Runnable() {
2636 public void run() {
2637 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2638 if (callbacks != null) {
2639 callbacks.bindAppWidget(widget);
2640 }
2641 }
2642 };
2643 if (postOnMainThread) {
2644 deferredBindRunnables.add(r);
2645 } else {
2646 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2647 }
2648 }
2649 }
2650
2651 /**
2652 * Binds all loaded data to actual views on the main thread.
2653 */
2654 private void bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath) {
2655 final long t = SystemClock.uptimeMillis();
2656 Runnable r;
2657
2658 // Don't use these two variables in any of the callback runnables.
2659 // Otherwise we hold a reference to them.
2660 final Callbacks oldCallbacks = mCallbacks.get();
2661 if (oldCallbacks == null) {
2662 // This launcher has exited and nobody bothered to tell us. Just bail.
2663 Log.w(TAG, "LoaderTask running with no launcher");
2664 return;
2665 }
2666
2667 // Save a copy of all the bg-thread collections
2668 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2669 ArrayList<LauncherAppWidgetInfo> appWidgets =
2670 new ArrayList<LauncherAppWidgetInfo>();
2671 HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
2672 HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
2673 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2674 synchronized (sBgLock) {
2675 workspaceItems.addAll(sBgWorkspaceItems);
2676 appWidgets.addAll(sBgAppWidgets);
2677 folders.putAll(sBgFolders);
2678 itemsIdMap.putAll(sBgItemsIdMap);
2679 orderedScreenIds.addAll(sBgWorkspaceScreens);
2680 }
2681
2682 final boolean isLoadingSynchronously =
2683 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2684 int currScreen = isLoadingSynchronously ? synchronizeBindPage :
2685 oldCallbacks.getCurrentWorkspaceScreen();
2686 if (currScreen >= orderedScreenIds.size()) {
2687 // There may be no workspace screens (just hotseat items and an empty page).
2688 currScreen = PagedView.INVALID_RESTORE_PAGE;
2689 }
2690 final int currentScreen = currScreen;
2691 final long currentScreenId = currentScreen < 0
2692 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
2693
2694 // Load all the items that are on the current page first (and in the process, unbind
2695 // all the existing workspace items before we call startBinding() below.
2696 unbindWorkspaceItemsOnMainThread();
2697
2698 // Separate the items that are on the current screen, and all the other remaining items
2699 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2700 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2701 ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
2702 new ArrayList<LauncherAppWidgetInfo>();
2703 ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
2704 new ArrayList<LauncherAppWidgetInfo>();
2705 HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
2706 HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
2707
2708 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2709 otherWorkspaceItems);
2710 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2711 otherAppWidgets);
2712 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
2713 otherFolders);
2714 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2715 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2716
2717 // Tell the workspace that we're about to start binding items
2718 r = new Runnable() {
2719 public void run() {
2720 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2721 if (callbacks != null) {
2722 callbacks.startBinding();
2723 }
2724 }
2725 };
2726 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2727
2728 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2729
2730 // Load items on the current page
2731 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
2732 currentFolders, null);
2733 if (isLoadingSynchronously) {
2734 r = new Runnable() {
2735 public void run() {
2736 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2737 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2738 callbacks.onPageBoundSynchronously(currentScreen);
2739 }
2740 }
2741 };
2742 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2743 }
2744
2745 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2746 // work until after the first render)
2747 mDeferredBindRunnables.clear();
2748 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
2749 (isLoadingSynchronously ? mDeferredBindRunnables : null));
2750
2751 // Tell the workspace that we're done binding items
2752 r = new Runnable() {
2753 public void run() {
2754 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2755 if (callbacks != null) {
2756 callbacks.finishBindingItems(isUpgradePath);
2757 }
2758
2759 // If we're profiling, ensure this is the last thing in the queue.
2760 if (DEBUG_LOADERS) {
2761 Log.d(TAG, "bound workspace in "
2762 + (SystemClock.uptimeMillis()-t) + "ms");
2763 }
2764
2765 mIsLoadingAndBindingWorkspace = false;
2766 }
2767 };
2768 if (isLoadingSynchronously) {
2769 mDeferredBindRunnables.add(r);
2770 } else {
2771 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2772 }
2773 }
2774
2775 private void loadAndBindAllApps() {
2776 if (DEBUG_LOADERS) {
2777 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2778 }
2779 if (!mAllAppsLoaded) {
2780 loadAllApps();
2781 synchronized (LoaderTask.this) {
2782 if (mStopped) {
2783 return;
2784 }
2785 mAllAppsLoaded = true;
2786 }
2787 } else {
2788 onlyBindAllApps();
2789 }
2790 }
2791
2792 private void onlyBindAllApps() {
2793 final Callbacks oldCallbacks = mCallbacks.get();
2794 if (oldCallbacks == null) {
2795 // This launcher has exited and nobody bothered to tell us. Just bail.
2796 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2797 return;
2798 }
2799
2800 // shallow copy
2801 @SuppressWarnings("unchecked")
2802 final ArrayList<AppInfo> list
2803 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2804 Runnable r = new Runnable() {
2805 public void run() {
2806 final long t = SystemClock.uptimeMillis();
2807 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2808 if (callbacks != null) {
2809 callbacks.bindAllApplications(list);
2810 }
2811 if (DEBUG_LOADERS) {
2812 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2813 + (SystemClock.uptimeMillis()-t) + "ms");
2814 }
2815 }
2816 };
2817 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2818 if (isRunningOnMainThread) {
2819 r.run();
2820 } else {
2821 mHandler.post(r);
2822 }
2823 }
2824
2825 private void loadAllApps() {
2826 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2827
2828 final Callbacks oldCallbacks = mCallbacks.get();
2829 if (oldCallbacks == null) {
2830 // This launcher has exited and nobody bothered to tell us. Just bail.
2831 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2832 return;
2833 }
2834
2835 final PackageManager packageManager = mContext.getPackageManager();
2836 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
2837 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2838
2839 // Clear the list of apps
2840 mBgAllAppsList.clear();
2841
2842 // Query for the set of apps
2843 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2844 List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
2845 if (DEBUG_LOADERS) {
2846 Log.d(TAG, "queryIntentActivities took "
2847 + (SystemClock.uptimeMillis()-qiaTime) + "ms");
2848 Log.d(TAG, "queryIntentActivities got " + apps.size() + " apps");
2849 }
2850 // Fail if we don't have any apps
2851 if (apps == null || apps.isEmpty()) {
2852 return;
2853 }
2854 // Sort the applications by name
2855 final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2856 Collections.sort(apps,
2857 new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
2858 if (DEBUG_LOADERS) {
2859 Log.d(TAG, "sort took "
2860 + (SystemClock.uptimeMillis()-sortTime) + "ms");
2861 }
2862
2863 // Create the ApplicationInfos
2864 for (int i = 0; i < apps.size(); i++) {
2865 ResolveInfo app = apps.get(i);
2866 // This builds the icon bitmaps.
2867 mBgAllAppsList.add(new AppInfo(packageManager, app,
2868 mIconCache, mLabelCache));
2869 }
2870
2871 // Huh? Shouldn't this be inside the Runnable below?
2872 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2873 mBgAllAppsList.added = new ArrayList<AppInfo>();
2874
2875 // Post callback on main thread
2876 mHandler.post(new Runnable() {
2877 public void run() {
2878 final long bindTime = SystemClock.uptimeMillis();
2879 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2880 if (callbacks != null) {
2881 callbacks.bindAllApplications(added);
2882 if (DEBUG_LOADERS) {
2883 Log.d(TAG, "bound " + added.size() + " apps in "
2884 + (SystemClock.uptimeMillis() - bindTime) + "ms");
2885 }
2886 } else {
2887 Log.i(TAG, "not binding apps: no Launcher activity");
2888 }
2889 }
2890 });
2891
2892 if (DEBUG_LOADERS) {
2893 Log.d(TAG, "Icons processed in "
2894 + (SystemClock.uptimeMillis() - loadTime) + "ms");
2895 }
2896 }
2897
2898 public void dumpState() {
2899 synchronized (sBgLock) {
2900 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2901 Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
2902 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2903 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2904 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2905 }
2906 }
2907 }
2908
2909 void enqueuePackageUpdated(PackageUpdatedTask task) {
2910 sWorker.post(task);
2911 }
2912
2913 private class PackageUpdatedTask implements Runnable {
2914 int mOp;
2915 String[] mPackages;
2916
2917 public static final int OP_NONE = 0;
2918 public static final int OP_ADD = 1;
2919 public static final int OP_UPDATE = 2;
2920 public static final int OP_REMOVE = 3; // uninstlled
2921 public static final int OP_UNAVAILABLE = 4; // external media unmounted
2922
2923
2924 public PackageUpdatedTask(int op, String[] packages) {
2925 mOp = op;
2926 mPackages = packages;
2927 }
2928
2929 public void run() {
2930 final Context context = mApp.getContext();
2931
2932 final String[] packages = mPackages;
2933 final int N = packages.length;
2934 switch (mOp) {
2935 case OP_ADD:
2936 for (int i=0; i<N; i++) {
2937 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
2938 mBgAllAppsList.addPackage(context, packages[i]);
2939 }
2940 break;
2941 case OP_UPDATE:
2942 for (int i=0; i<N; i++) {
2943 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
2944 mBgAllAppsList.updatePackage(context, packages[i]);
2945 WidgetPreviewLoader.removePackageFromDb(
2946 mApp.getWidgetPreviewCacheDb(), packages[i]);
2947 }
2948 break;
2949 case OP_REMOVE:
2950 case OP_UNAVAILABLE:
2951 for (int i=0; i<N; i++) {
2952 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
2953 mBgAllAppsList.removePackage(packages[i]);
2954 WidgetPreviewLoader.removePackageFromDb(
2955 mApp.getWidgetPreviewCacheDb(), packages[i]);
2956 }
2957 break;
2958 }
2959
2960 ArrayList<AppInfo> added = null;
2961 ArrayList<AppInfo> modified = null;
2962 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
2963
2964 if (mBgAllAppsList.added.size() > 0) {
2965 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
2966 mBgAllAppsList.added.clear();
2967 }
2968 if (mBgAllAppsList.modified.size() > 0) {
2969 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
2970 mBgAllAppsList.modified.clear();
2971 }
2972 if (mBgAllAppsList.removed.size() > 0) {
2973 removedApps.addAll(mBgAllAppsList.removed);
2974 mBgAllAppsList.removed.clear();
2975 }
2976
2977 final Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
2978 if (callbacks == null) {
2979 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
2980 return;
2981 }
2982
2983 if (added != null) {
2984 // Ensure that we add all the workspace applications to the db
2985 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
2986 if (!LauncherAppState.isDisableAllApps()) {
2987 addAndBindAddedApps(context, new ArrayList<ItemInfo>(), cb, added);
2988 } else {
2989 final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added);
2990 addAndBindAddedApps(context, addedInfos, cb, added);
2991 }
2992 }
2993 if (modified != null) {
2994 final ArrayList<AppInfo> modifiedFinal = modified;
2995
2996 // Update the launcher db to reflect the changes
2997 for (AppInfo a : modifiedFinal) {
2998 ArrayList<ItemInfo> infos =
2999 getItemInfoForComponentName(a.componentName);
3000 for (ItemInfo i : infos) {
3001 if (isShortcutInfoUpdateable(i)) {
3002 ShortcutInfo info = (ShortcutInfo) i;
3003 info.title = a.title.toString();
3004 updateItemInDatabase(context, info);
3005 }
3006 }
3007 }
3008
3009 mHandler.post(new Runnable() {
3010 public void run() {
3011 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
3012 if (callbacks == cb && cb != null) {
3013 callbacks.bindAppsUpdated(modifiedFinal);
3014 }
3015 }
3016 });
3017 }
3018
3019 final ArrayList<String> removedPackageNames =
3020 new ArrayList<String>();
3021 if (mOp == OP_REMOVE) {
3022 // Mark all packages in the broadcast to be removed
3023 removedPackageNames.addAll(Arrays.asList(packages));
3024 } else if (mOp == OP_UPDATE) {
3025 // Mark disabled packages in the broadcast to be removed
3026 final PackageManager pm = context.getPackageManager();
3027 for (int i=0; i<N; i++) {
3028 if (isPackageDisabled(pm, packages[i])) {
3029 removedPackageNames.add(packages[i]);
3030 }
3031 }
3032 }
3033 // Remove all the components associated with this package
3034 for (String pn : removedPackageNames) {
3035 ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn);
3036 for (ItemInfo i : infos) {
3037 deleteItemFromDatabase(context, i);
3038 }
3039 }
3040 // Remove all the specific components
3041 for (AppInfo a : removedApps) {
3042 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName);
3043 for (ItemInfo i : infos) {
3044 deleteItemFromDatabase(context, i);
3045 }
3046 }
3047 if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
3048 // Remove any queued items from the install queue
3049 String spKey = LauncherAppState.getSharedPreferencesKey();
3050 SharedPreferences sp =
3051 context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
3052 InstallShortcutReceiver.removeFromInstallQueue(sp, removedPackageNames);
3053 // Call the components-removed callback
3054 mHandler.post(new Runnable() {
3055 public void run() {
3056 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
3057 if (callbacks == cb && cb != null) {
3058 callbacks.bindComponentsRemoved(removedPackageNames, removedApps);
3059 }
3060 }
3061 });
3062 }
3063
3064 final ArrayList<Object> widgetsAndShortcuts =
3065 getSortedWidgetsAndShortcuts(context);
3066 mHandler.post(new Runnable() {
3067 @Override
3068 public void run() {
3069 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
3070 if (callbacks == cb && cb != null) {
3071 callbacks.bindPackagesUpdated(widgetsAndShortcuts);
3072 }
3073 }
3074 });
3075
3076 // Write all the logs to disk
3077 mHandler.post(new Runnable() {
3078 public void run() {
3079 Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
3080 if (callbacks == cb && cb != null) {
3081 callbacks.dumpLogsToLocalData();
3082 }
3083 }
3084 });
3085 }
3086 }
3087
3088 // Returns a list of ResolveInfos/AppWindowInfos in sorted order
3089 public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context) {
3090 PackageManager packageManager = context.getPackageManager();
3091 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
3092 widgetsAndShortcuts.addAll(AppWidgetManager.getInstance(context).getInstalledProviders());
3093 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
3094 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
3095 Collections.sort(widgetsAndShortcuts,
3096 new LauncherModel.WidgetAndShortcutNameComparator(packageManager));
3097 return widgetsAndShortcuts;
3098 }
3099
3100 private static boolean isPackageDisabled(PackageManager pm, String packageName) {
3101 try {
3102 PackageInfo pi = pm.getPackageInfo(packageName, 0);
3103 return !pi.applicationInfo.enabled;
3104 } catch (NameNotFoundException e) {
3105 // Fall through
3106 }
3107 return false;
3108 }
3109
3110 public static boolean isValidPackageComponent(PackageManager pm, ComponentName cn) {
3111 if (cn == null) {
3112 return false;
3113 }
3114 if (isPackageDisabled(pm, cn.getPackageName())) {
3115 return false;
3116 }
3117
3118 try {
3119 // Check the activity
3120 PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0);
3121 return (pm.getActivityInfo(cn, 0) != null);
3122 } catch (NameNotFoundException e) {
3123 return false;
3124 }
3125 }
3126
3127 /**
3128 * Make an ShortcutInfo object for a restored application or shortcut item that points
3129 * to a package that is not yet installed on the system.
3130 */
3131 public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent) {
3132 final ShortcutInfo info = new ShortcutInfo();
3133 info.usingFallbackIcon = true;
3134 info.setIcon(getFallbackIcon());
3135 if (cursor != null) {
3136 info.title = cursor.getString(titleIndex);
3137 } else {
3138 info.title = "";
3139 }
3140 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3141 info.restoredIntent = intent;
3142 return info;
3143 }
3144
3145 /**
3146 * Make an Intent object for a restored application or shortcut item that points
3147 * to the market page for the item.
3148 */
3149 private Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3150 final boolean debug = false;
3151 ComponentName componentName = intent.getComponent();
3152 Intent marketIntent = new Intent(Intent.ACTION_VIEW);
3153 Uri marketUri = new Uri.Builder()
3154 .scheme("market")
3155 .authority("details")
3156 .appendQueryParameter("id", componentName.getPackageName())
3157 .build();
3158 if (debug) Log.d(TAG, "manufactured intent uri: " + marketUri.toString());
3159 marketIntent.setData(marketUri);
3160 return marketIntent;
3161 }
3162
3163 /**
3164 * This is called from the code that adds shortcuts from the intent receiver. This
3165 * doesn't have a Cursor, but
3166 */
3167 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) {
3168 return getShortcutInfo(manager, intent, context, null, -1, -1, null);
3169 }
3170
3171 /**
3172 * Make an ShortcutInfo object for a shortcut that is an application.
3173 *
3174 * If c is not null, then it will be used to fill in missing data like the title and icon.
3175 */
3176 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context,
3177 Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) {
3178 ComponentName componentName = intent.getComponent();
3179 final ShortcutInfo info = new ShortcutInfo();
3180 if (componentName != null && !isValidPackageComponent(manager, componentName)) {
3181 Log.d(TAG, "Invalid package found in getShortcutInfo: " + componentName);
3182 return null;
3183 } else {
3184 try {
3185 PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0);
3186 info.initFlagsAndFirstInstallTime(pi);
3187 } catch (NameNotFoundException e) {
3188 Log.d(TAG, "getPackInfo failed for package " +
3189 componentName.getPackageName());
3190 }
3191 }
3192
3193 // TODO: See if the PackageManager knows about this case. If it doesn't
3194 // then return null & delete this.
3195
3196 // the resource -- This may implicitly give us back the fallback icon,
3197 // but don't worry about that. All we're doing with usingFallbackIcon is
3198 // to avoid saving lots of copies of that in the database, and most apps
3199 // have icons anyway.
3200
3201 // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and
3202 // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info
3203 // via resolveActivity().
3204 Bitmap icon = null;
3205 ResolveInfo resolveInfo = null;
3206 ComponentName oldComponent = intent.getComponent();
3207 Intent newIntent = new Intent(intent.getAction(), null);
3208 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3209 newIntent.setPackage(oldComponent.getPackageName());
3210 List<ResolveInfo> infos = manager.queryIntentActivities(newIntent, 0);
3211 for (ResolveInfo i : infos) {
3212 ComponentName cn = new ComponentName(i.activityInfo.packageName,
3213 i.activityInfo.name);
3214 if (cn.equals(oldComponent)) {
3215 resolveInfo = i;
3216 }
3217 }
3218 if (resolveInfo == null) {
3219 resolveInfo = manager.resolveActivity(intent, 0);
3220 }
3221 if (resolveInfo != null) {
3222 icon = mIconCache.getIcon(componentName, resolveInfo, labelCache);
3223 }
3224 // the db
3225 if (icon == null) {
3226 if (c != null) {
3227 icon = getIconFromCursor(c, iconIndex, context);
3228 }
3229 }
3230 // the fallback icon
3231 if (icon == null) {
3232 icon = getFallbackIcon();
3233 info.usingFallbackIcon = true;
3234 }
3235 info.setIcon(icon);
3236
3237 // from the resource
3238 if (resolveInfo != null) {
3239 ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo);
3240 if (labelCache != null && labelCache.containsKey(key)) {
3241 info.title = labelCache.get(key);
3242 } else {
3243 info.title = resolveInfo.activityInfo.loadLabel(manager);
3244 if (labelCache != null) {
3245 labelCache.put(key, info.title);
3246 }
3247 }
3248 }
3249 // from the db
3250 if (info.title == null) {
3251 if (c != null) {
3252 info.title = c.getString(titleIndex);
3253 }
3254 }
3255 // fall back to the class name of the activity
3256 if (info.title == null) {
3257 info.title = componentName.getClassName();
3258 }
3259 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3260 return info;
3261 }
3262
3263 static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos,
3264 ItemInfoFilter f) {
3265 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3266 for (ItemInfo i : infos) {
3267 if (i instanceof ShortcutInfo) {
3268 ShortcutInfo info = (ShortcutInfo) i;
3269 ComponentName cn = info.intent.getComponent();
3270 if (cn != null && f.filterItem(null, info, cn)) {
3271 filtered.add(info);
3272 }
3273 } else if (i instanceof FolderInfo) {
3274 FolderInfo info = (FolderInfo) i;
3275 for (ShortcutInfo s : info.contents) {
3276 ComponentName cn = s.intent.getComponent();
3277 if (cn != null && f.filterItem(info, s, cn)) {
3278 filtered.add(s);
3279 }
3280 }
3281 } else if (i instanceof LauncherAppWidgetInfo) {
3282 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3283 ComponentName cn = info.providerName;
3284 if (cn != null && f.filterItem(null, info, cn)) {
3285 filtered.add(info);
3286 }
3287 }
3288 }
3289 return new ArrayList<ItemInfo>(filtered);
3290 }
3291
3292 private ArrayList<ItemInfo> getItemInfoForPackageName(final String pn) {
3293 ItemInfoFilter filter = new ItemInfoFilter() {
3294 @Override
3295 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3296 return cn.getPackageName().equals(pn);
3297 }
3298 };
3299 return filterItemInfos(sBgItemsIdMap.values(), filter);
3300 }
3301
3302 private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname) {
3303 ItemInfoFilter filter = new ItemInfoFilter() {
3304 @Override
3305 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3306 return cn.equals(cname);
3307 }
3308 };
3309 return filterItemInfos(sBgItemsIdMap.values(), filter);
3310 }
3311
3312 public static boolean isShortcutInfoUpdateable(ItemInfo i) {
3313 if (i instanceof ShortcutInfo) {
3314 ShortcutInfo info = (ShortcutInfo) i;
3315 // We need to check for ACTION_MAIN otherwise getComponent() might
3316 // return null for some shortcuts (for instance, for shortcuts to
3317 // web pages.)
3318 Intent intent = info.intent;
3319 ComponentName name = intent.getComponent();
3320 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
3321 Intent.ACTION_MAIN.equals(intent.getAction()) && name != null) {
3322 return true;
3323 }
3324 // placeholder shortcuts get special treatment, let them through too.
3325 if (info.getRestoredIntent() != null) {
3326 return true;
3327 }
3328 }
3329 return false;
3330 }
3331
3332 /**
3333 * Make an ShortcutInfo object for a shortcut that isn't an application.
3334 */
3335 private ShortcutInfo getShortcutInfo(Cursor c, Context context,
3336 int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
3337 int titleIndex) {
3338
3339 Bitmap icon = null;
3340 final ShortcutInfo info = new ShortcutInfo();
3341 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3342
3343 // TODO: If there's an explicit component and we can't install that, delete it.
3344
3345 info.title = c.getString(titleIndex);
3346
3347 int iconType = c.getInt(iconTypeIndex);
3348 switch (iconType) {
3349 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
3350 String packageName = c.getString(iconPackageIndex);
3351 String resourceName = c.getString(iconResourceIndex);
3352 PackageManager packageManager = context.getPackageManager();
3353 info.customIcon = false;
3354 // the resource
3355 try {
3356 Resources resources = packageManager.getResourcesForApplication(packageName);
3357 if (resources != null) {
3358 final int id = resources.getIdentifier(resourceName, null, null);
3359 icon = Utilities.createIconBitmap(
3360 mIconCache.getFullResIcon(resources, id), context);
3361 }
3362 } catch (Exception e) {
3363 // drop this. we have other places to look for icons
3364 }
3365 // the db
3366 if (icon == null) {
3367 icon = getIconFromCursor(c, iconIndex, context);
3368 }
3369 // the fallback icon
3370 if (icon == null) {
3371 icon = getFallbackIcon();
3372 info.usingFallbackIcon = true;
3373 }
3374 break;
3375 case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
3376 icon = getIconFromCursor(c, iconIndex, context);
3377 if (icon == null) {
3378 icon = getFallbackIcon();
3379 info.customIcon = false;
3380 info.usingFallbackIcon = true;
3381 } else {
3382 info.customIcon = true;
3383 }
3384 break;
3385 default:
3386 icon = getFallbackIcon();
3387 info.usingFallbackIcon = true;
3388 info.customIcon = false;
3389 break;
3390 }
3391 info.setIcon(icon);
3392 return info;
3393 }
3394
3395 Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) {
3396 @SuppressWarnings("all") // suppress dead code warning
3397 final boolean debug = false;
3398 if (debug) {
3399 Log.d(TAG, "getIconFromCursor app="
3400 + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE)));
3401 }
3402 byte[] data = c.getBlob(iconIndex);
3403 try {
3404 return Utilities.createIconBitmap(
3405 BitmapFactory.decodeByteArray(data, 0, data.length), context);
3406 } catch (Exception e) {
3407 return null;
3408 }
3409 }
3410
3411 ShortcutInfo addShortcut(Context context, Intent data, long container, int screen,
3412 int cellX, int cellY, boolean notify) {
3413 final ShortcutInfo info = infoFromShortcutIntent(context, data, null);
3414 if (info == null) {
3415 return null;
3416 }
3417 addItemToDatabase(context, info, container, screen, cellX, cellY, notify);
3418
3419 return info;
3420 }
3421
3422 /**
3423 * Attempts to find an AppWidgetProviderInfo that matches the given component.
3424 */
3425 AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,
3426 ComponentName component) {
3427 List<AppWidgetProviderInfo> widgets =
3428 AppWidgetManager.getInstance(context).getInstalledProviders();
3429 for (AppWidgetProviderInfo info : widgets) {
3430 if (info.provider.equals(component)) {
3431 return info;
3432 }
3433 }
3434 return null;
3435 }
3436
3437 /**
3438 * Returns a list of all the widgets that can handle configuration with a particular mimeType.
3439 */
3440 List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) {
3441 final PackageManager packageManager = context.getPackageManager();
3442 final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities =
3443 new ArrayList<WidgetMimeTypeHandlerData>();
3444
3445 final Intent supportsIntent =
3446 new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE);
3447 supportsIntent.setType(mimeType);
3448
3449 // Create a set of widget configuration components that we can test against
3450 final List<AppWidgetProviderInfo> widgets =
3451 AppWidgetManager.getInstance(context).getInstalledProviders();
3452 final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget =
3453 new HashMap<ComponentName, AppWidgetProviderInfo>();
3454 for (AppWidgetProviderInfo info : widgets) {
3455 configurationComponentToWidget.put(info.configure, info);
3456 }
3457
3458 // Run through each of the intents that can handle this type of clip data, and cross
3459 // reference them with the components that are actual configuration components
3460 final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent,
3461 PackageManager.MATCH_DEFAULT_ONLY);
3462 for (ResolveInfo info : activities) {
3463 final ActivityInfo activityInfo = info.activityInfo;
3464 final ComponentName infoComponent = new ComponentName(activityInfo.packageName,
3465 activityInfo.name);
3466 if (configurationComponentToWidget.containsKey(infoComponent)) {
3467 supportedConfigurationActivities.add(
3468 new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info,
3469 configurationComponentToWidget.get(infoComponent)));
3470 }
3471 }
3472 return supportedConfigurationActivities;
3473 }
3474
3475 ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) {
3476 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3477 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3478 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3479
3480 if (intent == null) {
3481 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3482 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3483 return null;
3484 }
3485
3486 Bitmap icon = null;
3487 boolean customIcon = false;
3488 ShortcutIconResource iconResource = null;
3489
3490 if (bitmap != null && bitmap instanceof Bitmap) {
3491 icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context);
3492 customIcon = true;
3493 } else {
3494 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3495 if (extra != null && extra instanceof ShortcutIconResource) {
3496 try {
3497 iconResource = (ShortcutIconResource) extra;
3498 final PackageManager packageManager = context.getPackageManager();
3499 Resources resources = packageManager.getResourcesForApplication(
3500 iconResource.packageName);
3501 final int id = resources.getIdentifier(iconResource.resourceName, null, null);
3502 icon = Utilities.createIconBitmap(
3503 mIconCache.getFullResIcon(resources, id), context);
3504 } catch (Exception e) {
3505 Log.w(TAG, "Could not load shortcut icon: " + extra);
3506 }
3507 }
3508 }
3509
3510 final ShortcutInfo info = new ShortcutInfo();
3511
3512 if (icon == null) {
3513 if (fallbackIcon != null) {
3514 icon = fallbackIcon;
3515 } else {
3516 icon = getFallbackIcon();
3517 info.usingFallbackIcon = true;
3518 }
3519 }
3520 info.setIcon(icon);
3521
3522 info.title = name;
3523 info.intent = intent;
3524 info.customIcon = customIcon;
3525 info.iconResource = iconResource;
3526
3527 return info;
3528 }
3529
3530 boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c,
3531 int iconIndex) {
3532 // If apps can't be on SD, don't even bother.
3533 if (!mAppsCanBeOnRemoveableStorage) {
3534 return false;
3535 }
3536 // If this icon doesn't have a custom icon, check to see
3537 // what's stored in the DB, and if it doesn't match what
3538 // we're going to show, store what we are going to show back
3539 // into the DB. We do this so when we're loading, if the
3540 // package manager can't find an icon (for example because
3541 // the app is on SD) then we can use that instead.
3542 if (!info.customIcon && !info.usingFallbackIcon) {
3543 cache.put(info, c.getBlob(iconIndex));
3544 return true;
3545 }
3546 return false;
3547 }
3548 void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) {
3549 boolean needSave = false;
3550 try {
3551 if (data != null) {
3552 Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length);
3553 Bitmap loaded = info.getIcon(mIconCache);
3554 needSave = !saved.sameAs(loaded);
3555 } else {
3556 needSave = true;
3557 }
3558 } catch (Exception e) {
3559 needSave = true;
3560 }
3561 if (needSave) {
3562 Log.d(TAG, "going to save icon bitmap for info=" + info);
3563 // This is slower than is ideal, but this only happens once
3564 // or when the app is updated with a new icon.
3565 updateItemInDatabase(context, info);
3566 }
3567 }
3568
3569 /**
3570 * Return an existing FolderInfo object if we have encountered this ID previously,
3571 * or make a new one.
3572 */
3573 private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
3574 // See if a placeholder was created for us already
3575 FolderInfo folderInfo = folders.get(id);
3576 if (folderInfo == null) {
3577 // No placeholder -- create a new instance
3578 folderInfo = new FolderInfo();
3579 folders.put(id, folderInfo);
3580 }
3581 return folderInfo;
3582 }
3583
3584 public static final Comparator<AppInfo> getAppNameComparator() {
3585 final Collator collator = Collator.getInstance();
3586 return new Comparator<AppInfo>() {
3587 public final int compare(AppInfo a, AppInfo b) {
3588 int result = collator.compare(a.title.toString().trim(),
3589 b.title.toString().trim());
3590 if (result == 0) {
3591 result = a.componentName.compareTo(b.componentName);
3592 }
3593 return result;
3594 }
3595 };
3596 }
3597 public static final Comparator<AppInfo> APP_INSTALL_TIME_COMPARATOR
3598 = new Comparator<AppInfo>() {
3599 public final int compare(AppInfo a, AppInfo b) {
3600 if (a.firstInstallTime < b.firstInstallTime) return 1;
3601 if (a.firstInstallTime > b.firstInstallTime) return -1;
3602 return 0;
3603 }
3604 };
3605 public static final Comparator<AppWidgetProviderInfo> getWidgetNameComparator() {
3606 final Collator collator = Collator.getInstance();
3607 return new Comparator<AppWidgetProviderInfo>() {
3608 public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) {
3609 return collator.compare(a.label.toString().trim(), b.label.toString().trim());
3610 }
3611 };
3612 }
3613 static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) {
3614 if (info.activityInfo != null) {
3615 return new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
3616 } else {
3617 return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
3618 }
3619 }
3620 public static class ShortcutNameComparator implements Comparator<ResolveInfo> {
3621 private Collator mCollator;
3622 private PackageManager mPackageManager;
3623 private HashMap<Object, CharSequence> mLabelCache;
3624 ShortcutNameComparator(PackageManager pm) {
3625 mPackageManager = pm;
3626 mLabelCache = new HashMap<Object, CharSequence>();
3627 mCollator = Collator.getInstance();
3628 }
3629 ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) {
3630 mPackageManager = pm;
3631 mLabelCache = labelCache;
3632 mCollator = Collator.getInstance();
3633 }
3634 public final int compare(ResolveInfo a, ResolveInfo b) {
3635 CharSequence labelA, labelB;
3636 ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a);
3637 ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b);
3638 if (mLabelCache.containsKey(keyA)) {
3639 labelA = mLabelCache.get(keyA);
3640 } else {
3641 labelA = a.loadLabel(mPackageManager).toString().trim();
3642
3643 mLabelCache.put(keyA, labelA);
3644 }
3645 if (mLabelCache.containsKey(keyB)) {
3646 labelB = mLabelCache.get(keyB);
3647 } else {
3648 labelB = b.loadLabel(mPackageManager).toString().trim();
3649
3650 mLabelCache.put(keyB, labelB);
3651 }
3652 return mCollator.compare(labelA, labelB);
3653 }
3654 }
3655 public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
3656 private Collator mCollator;
3657 private PackageManager mPackageManager;
3658 private HashMap<Object, String> mLabelCache;
3659 WidgetAndShortcutNameComparator(PackageManager pm) {
3660 mPackageManager = pm;
3661 mLabelCache = new HashMap<Object, String>();
3662 mCollator = Collator.getInstance();
3663 }
3664 public final int compare(Object a, Object b) {
3665 String labelA, labelB;
3666 if (mLabelCache.containsKey(a)) {
3667 labelA = mLabelCache.get(a);
3668 } else {
3669 labelA = (a instanceof AppWidgetProviderInfo) ?
3670 ((AppWidgetProviderInfo) a).label :
3671 ((ResolveInfo) a).loadLabel(mPackageManager).toString().trim();
3672 mLabelCache.put(a, labelA);
3673 }
3674 if (mLabelCache.containsKey(b)) {
3675 labelB = mLabelCache.get(b);
3676 } else {
3677 labelB = (b instanceof AppWidgetProviderInfo) ?
3678 ((AppWidgetProviderInfo) b).label :
3679 ((ResolveInfo) b).loadLabel(mPackageManager).toString().trim();
3680 mLabelCache.put(b, labelB);
3681 }
3682 return mCollator.compare(labelA, labelB);
3683 }
3684 }
3685
3686 public void dumpState() {
3687 Log.d(TAG, "mCallbacks=" + mCallbacks);
3688 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3689 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3690 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3691 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3692 if (mLoaderTask != null) {
3693 mLoaderTask.dumpState();
3694 } else {
3695 Log.d(TAG, "mLoaderTask=null");
3696 }
3697 }
3698 } |
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.launcher3;
17
18 import android.app.SearchManager;
19 import android.appwidget.AppWidgetManager;
20 import android.appwidget.AppWidgetProviderInfo;
21 import android.content.*;
22 import android.content.Intent.ShortcutIconResource;
23 import android.content.pm.ActivityInfo;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager.NameNotFoundException;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.content.res.Configuration;
29 import android.content.res.Resources;
30 import android.database.Cursor;
31 import android.graphics.Bitmap;
32 import android.graphics.BitmapFactory;
33 import android.net.Uri;
34 import android.os.Environment;
35 import android.os.Handler;
36 import android.os.HandlerThread;
37 import android.os.Parcelable;
38 import android.os.Process;
39 import android.os.RemoteException;
40 import android.os.SystemClock;
41 import android.provider.BaseColumns;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import android.util.Pair;
45 import com.android.launcher3.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
46 import java.lang.ref.WeakReference;
47 import java.net.URISyntaxException;
48 import java.text.Collator;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.Comparator;
54 import java.util.HashMap;
55 import java.util.HashSet;
56 import java.util.Iterator;
57 import java.util.List;
58 import java.util.Set;
59 import java.util.TreeMap;
60 import java.util.concurrent.atomic.AtomicBoolean;
61
62
63 /**
64 * Maintains in-memory state of the Launcher. It is expected that there should be only one
65 * LauncherModel object held in a static. Also provide APIs for updating the database state
66 * for the Launcher.
67 */
68 public class LauncherModel extends BroadcastReceiver {
69 static final boolean DEBUG_LOADERS = false;
70
71 static final String TAG = "Launcher.Model";
72
73 // true = use a "More Apps" folder for non-workspace apps on upgrade
74 // false = strew non-workspace apps across the workspace on upgrade
75 // true = use a "More Apps" folder for non-workspace apps on upgrade
76 // false = strew non-workspace apps across the workspace on upgrade
77 public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false;
78
79 public static final int LOADER_FLAG_NONE = 0;
80
81 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
82
83 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
84
85 // batch size for the workspace icons
86 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
87
88 private static final long INVALID_SCREEN_ID = -1L;
89
90 private final boolean mAppsCanBeOnRemoveableStorage;
91
92 private final boolean mOldContentProviderExists;
93
94 private final LauncherAppState mApp;
95
96 private final Object mLock = new Object();
97
98 private DeferredHandler mHandler = new DeferredHandler();
99
100 private LoaderTask mLoaderTask;
101
102 private boolean mIsLoaderTaskRunning;
103
104 private volatile boolean mFlushingWorkerThread;
105
106 // Specific runnable types that are run on the main thread deferred handler, this allows us to
107 // clear all queued binding runnables when the Launcher activity is destroyed.
108 private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
109
110 private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
111
112 private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
113
114 static {
115 sWorkerThread.start();
116 }
117
118 private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
119
120 // We start off with everything not loaded. After that, we assume that
121 // our monitoring of the package manager provides all updates and we never
122 // need to do a requery. These are only ever touched from the loader thread.
123 private boolean mWorkspaceLoaded;
124
125 private boolean mAllAppsLoaded;
126
127 // When we are loading pages synchronously, we can't just post the binding of items on the side
128 // pages as this delays the rotation process. Instead, we wait for a callback from the first
129 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
130 // a normal load, we also clear this set of Runnables.
131 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
132
133 private WeakReference<Callbacks> mCallbacks;
134
135 // < only access in worker thread >
136 // < only access in worker thread >
137 AllAppsList mBgAllAppsList;
138
139 // The lock that must be acquired before referencing any static bg data structures. Unlike
140 // other locks, this one can generally be held long-term because we never expect any of these
141 // static data structures to be referenced outside of the worker thread except on the first
142 // load after configuration change.
143 static final Object sBgLock = new Object();
144
145 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
146 // LauncherModel to their ids
147 static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
148
149 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
150 // created by LauncherModel that are directly on the home screen (however, no widgets or
151 // shortcuts within folders).
152 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
153
154 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
155 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
156 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
157 new ArrayList<LauncherAppWidgetInfo>();
158
159 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
160 static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
161
162 // sBgDbIconCache is the set of ItemInfos that need to have their icons updated in the database
163 static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>();
164
165 // sBgWorkspaceScreens is the ordered set of workspace screens.
166 // sBgWorkspaceScreens is the ordered set of workspace screens.
167 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
168
169 // </ only access in worker thread >
170 // </ only access in worker thread >
171
172 private IconCache mIconCache;
173
174 private Bitmap mDefaultIcon;
175
176 protected int mPreviousConfigMcc;
177
178 public interface Callbacks {
179 public abstract boolean setLoadOnResume();
180
181 public abstract int getCurrentWorkspaceScreen();
182
183 public abstract void startBinding();
184
185 public abstract void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, boolean forceAn🔵
186
187 public abstract void bindScreens(ArrayList<Long> orderedScreenIds);
188
189 public abstract void bindAddScreens(ArrayList<Long> orderedScreenIds);
190
191 public abstract void bindFolders(HashMap<Long, FolderInfo> folders);
192
193 public abstract void finishBindingItems(boolean upgradePath);
194
195 public abstract void bindAppWidget(LauncherAppWidgetInfo info);
196
197 public abstract void bindAllApplications(ArrayList<AppInfo> apps);
198
199 public abstract void bindAppsAdded(ArrayList<Long> newScreens, ArrayList<ItemInfo> addNotAnimated🔵
200
201 public abstract void bindAppsUpdated(ArrayList<AppInfo> apps);
202
203 public abstract void bindComponentsRemoved(ArrayList<String> packageNames, ArrayList<AppInfo> app🔵
204
205 public abstract void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
206
207 public abstract void bindSearchablesChanged();
208
209 public abstract boolean isAllAppsButtonRank(int rank);
210
211 public abstract void onPageBoundSynchronously(int page);
212
213 public abstract void dumpLogsToLocalData();
214 }
215
216 public interface ItemInfoFilter {
217 public abstract boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
218 }
219
220 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
221 Context context = app.getContext();
222 ContentResolver contentResolver = context.getContentResolver();
223 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
224 mOldContentProviderExists = contentResolver.acquireContentProviderClient(LauncherSettings.Favorit🔵
225 mApp = app;
226 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
227 mIconCache = iconCache;
228 final Resources res = context.getResources();
229 Configuration config = res.getConfiguration();
230 mPreviousConfigMcc = config.mcc;
231 }
232
233 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
234 * posted on the main thread handler. */
235 private void runOnMainThread(Runnable r) {
236 runOnMainThread(r, 0);
237 }
238
239 private void runOnMainThread(Runnable r, int type) {
240 if (sWorkerThread.getThreadId() == Process.myTid()) {
241 // If we are on the worker thread, post onto the main handler
242 mHandler.post(r);
243 } else {
244 r.run();
245 }
246 }
247
248 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
249 * posted on the worker thread handler. */
250 private static void runOnWorkerThread(Runnable r) {
251 if (sWorkerThread.getThreadId() == Process.myTid()) {
252 r.run();
253 } else {
254 // If we are not on the worker thread, then post to the worker handler
255 sWorker.post(r);
256 }
257 }
258
259 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
260 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
261 }
262
263 static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> items, int[] xy,
264 long screen) {
265 LauncherAppState app = LauncherAppState.getInstance();
266 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
267 final int xCount = (int) grid.numColumns;
268 final int yCount = (int) grid.numRows;
269 boolean[][] occupied = new boolean[xCount][yCount];
270
271 int cellX, cellY, spanX, spanY;
272 for (int i = 0; i < items.size(); ++i) {
273 final ItemInfo item = items.get(i);
274 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
275 if (item.screenId == screen) {
276 cellX = item.cellX;
277 cellY = item.cellY;
278 spanX = item.spanX;
279 spanY = item.spanY;
280 for (int x = cellX; 0 <= x && x < cellX + spanX && x < xCount; x++) {
281 for (int y = cellY; 0 <= y && y < cellY + spanY && y < yCount; y++) {
282 occupied[x][y] = true;
283 }
284 }
285 }
286 }
287 }
288
289 return CellLayout.findVacantCell(xy, 1, 1, xCount, yCount, occupied);
290 }
291
292 static Pair<Long, int[]> findNextAvailableIconSpace(Context context, String name,
293 Intent launchIntent,
294 int firstScreenIndex,
295 ArrayList<Long> workspaceScreens) {
296 // Lock on the app so that we don't try and get the items while apps are being added
297 LauncherAppState app = LauncherAppState.getInstance();
298 LauncherModel model = app.getModel();
299 boolean found = false;
300 synchronized (app) {
301 if (sWorkerThread.getThreadId() != Process.myTid()) {
302 // Flush the LauncherModel worker thread, so that if we just did another
303 // processInstallShortcut, we give it time for its shortcut to get added to the
304 // database (getItemsInLocalCoordinates reads the database)
305 model.flushWorkerThread();
306 }
307 final ArrayList<ItemInfo> items = LauncherModel.getItemsInLocalCoordinates(context);
308
309 // Try adding to the workspace screens incrementally, starting at the default or center
310 // screen and alternating between +1, -1, +2, -2, etc. (using ~ ceil(i/2f)*(-1)^(i-1))
311 firstScreenIndex = Math.min(firstScreenIndex, workspaceScreens.size());
312 int count = workspaceScreens.size();
313 for (int screen = firstScreenIndex; screen < count && !found; screen++) {
314 int[] tmpCoordinates = new int[2];
315 if (findNextAvailableIconSpaceInScreen(items, tmpCoordinates,
316 workspaceScreens.get(screen))) {
317 // Update the Launcher db
318 return new Pair<Long, int[]>(workspaceScreens.get(screen), tmpCoordinates);
319 }
320 }
321 }
322 return null;
323 }
324
325 public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps, final🔵
326 Callbacks cb = (mCallbacks != null) ? mCallbacks.get() : null;
327 addAndBindAddedApps(context, workspaceApps, cb, allAppsApps);
328 }
329
330 public void addAndBindAddedApps(final Context context, final ArrayList<ItemInfo> workspaceApps, final🔵
331 if ((workspaceApps == null) || (allAppsApps == null)) {
332 throw new RuntimeException("workspaceApps and allAppsApps must not be null");
333 }
334 if (workspaceApps.isEmpty() && allAppsApps.isEmpty()) {
335 return;
336 }
337 // Process the newly added applications and add them to the database first
338 Runnable r = new Runnable() {
339 public void run() {
340 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
341 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
342 final ArrayList<AppInfo> restoredAppsFinal = new ArrayList<AppInfo>();
343 // Get the list of workspace screens. We need to append to this list and
344 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
345 // called.
346 ArrayList<Long> workspaceScreens = new ArrayList<Long>();
347 TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(context);
348 for (Integer i : orderedScreens.keySet()) {
349 long screenId = orderedScreens.get(i);
350 workspaceScreens.add(screenId);
351 }
352 synchronized(sBgLock) {
353 Iterator<ItemInfo> iter = workspaceApps.iterator();
354 while (iter.hasNext()) {
355 ItemInfo a = iter.next();
356 final String name = a.title.toString();
357 final Intent launchIntent = a.getIntent();
358 // Short-circuit this logic if the icon exists somewhere on the workspace
359 if (LauncherModel.shortcutExists(context, name, launchIntent)) {
360 // Only InstallShortcutReceiver sends us shortcutInfos, ignore them
361 if ((a instanceof AppInfo) && LauncherModel.appWasRestored(context, launchInt🔵
362 restoredAppsFinal.add(((AppInfo) (a)));
363 }
364 continue;
365 }
366 // Add this icon to the db, creating a new page if necessary. If there
367 // is only the empty page then we just add items to the first page.
368 // Otherwise, we add them to the next pages.
369 int startSearchPageIndex = (workspaceScreens.isEmpty()) ? 0 : 1;
370 Pair<Long, int[]> coords = LauncherModel.findNextAvailableIconSpace(context, name🔵
371 if (coords == null) {
372 LauncherProvider lp = LauncherAppState.getLauncherProvider();
373 // If we can't find a valid position, then just add a new screen.
374 // This takes time so we need to re-queue the add until the new
375 // page is added. Create as many screens as necessary to satisfy
376 // the startSearchPageIndex.
377 int numPagesToAdd = Math.max(1, (startSearchPageIndex + 1) - workspaceScreens🔵
378 while (numPagesToAdd > 0) {
379 long screenId = lp.generateNewScreenId();
380 // Save the screen id for binding in the workspace
381 workspaceScreens.add(screenId);
382 addedWorkspaceScreensFinal.add(screenId);
383 numPagesToAdd--;
384 }
385 // Find the coordinate again
386 coords = LauncherModel.findNextAvailableIconSpace(context, name, launchIntent🔵
387 }
388 if (coords == null) {
389 throw new RuntimeException("Coordinates should not be null");
390 }
391 ShortcutInfo shortcutInfo;
392 if (a instanceof ShortcutInfo) {
393 shortcutInfo = ((ShortcutInfo) (a));
394 } else if (a instanceof AppInfo) {
395 shortcutInfo = ((AppInfo) (a)).makeShortcut();
396 } else {
397 throw new RuntimeException("Unexpected info type");
398 }
399 // Add the shortcut to the db
400 addItemToDatabase(context, shortcutInfo, LauncherSettings.Favorites.CONTAINER_DES🔵
401 // Save the ShortcutInfo for binding in the workspace
402 addedShortcutsFinal.add(shortcutInfo);
403 }
404 }
405 // Update the workspace screens
406 updateWorkspaceScreenOrder(context, workspaceScreens);
407 if ((!addedShortcutsFinal.isEmpty()) || (!allAppsApps.isEmpty())) {
408 runOnMainThread(new Runnable() {
409 public void run() {
410 Callbacks cb = (mCallbacks != null) ? mCallbacks.get() : null;
411 if ((callbacks == cb) && (cb != null)) {
412 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
413 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
414 if (!addedShortcutsFinal.isEmpty()) {
415 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
416 long lastScreenId = info.screenId;
417 for (ItemInfo i : addedShortcutsFinal) {
418 if (i.screenId == lastScreenId) {
419 addAnimated.add(i);
420 } else {
421 addNotAnimated.add(i);
422 }
423 }
424 }
425 callbacks.bindAppsAdded(addedWorkspaceScreensFinal, addNotAnimated, addAn🔵
426 if (!restoredAppsFinal.isEmpty()) {
427 callbacks.bindAppsUpdated(restoredAppsFinal);
428 }
429 }
430 }
431 });
432 }
433 }
434 };
435 runOnWorkerThread(r);
436 }
437
438 public Bitmap getFallbackIcon() {
439 if (mDefaultIcon == null) {
440 final Context context = LauncherAppState.getInstance().getContext();
441 mDefaultIcon = Utilities.createIconBitmap(
442 mIconCache.getFullResDefaultActivityIcon(), context);
443 }
444 return Bitmap.createBitmap(mDefaultIcon);
445 }
446
447 public void unbindItemInfosAndClearQueuedBindRunnables() {
448 if (sWorkerThread.getThreadId() == Process.myTid()) {
449 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
450 "main thread");
451 }
452
453 // Clear any deferred bind runnables
454 mDeferredBindRunnables.clear();
455 // Remove any queued bind runnables
456 mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
457 // Unbind all the workspace items
458 unbindWorkspaceItemsOnMainThread();
459 }
460
461 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
462 void unbindWorkspaceItemsOnMainThread() {
463 // Ensure that we don't use the same workspace items data structure on the main thread
464 // by making a copy of workspace items first.
465 final ArrayList<ItemInfo> tmpWorkspaceItems = new ArrayList<ItemInfo>();
466 final ArrayList<ItemInfo> tmpAppWidgets = new ArrayList<ItemInfo>();
467 synchronized (sBgLock) {
468 tmpWorkspaceItems.addAll(sBgWorkspaceItems);
469 tmpAppWidgets.addAll(sBgAppWidgets);
470 }
471 Runnable r = new Runnable() {
472 @Override
473 public void run() {
474 for (ItemInfo item : tmpWorkspaceItems) {
475 item.unbind();
476 }
477 for (ItemInfo item : tmpAppWidgets) {
478 item.unbind();
479 }
480 }
481 };
482 runOnMainThread(r);
483 }
484
485 /**
486 * Adds an item to the DB if it was not created previously, or move it to a new
487 * <container, screen, cellX, cellY>
488 */
489 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
490 long screenId, int cellX, int cellY) {
491 if (item.container == ItemInfo.NO_ID) {
492 // From all apps
493 addItemToDatabase(context, item, container, screenId, cellX, cellY, false);
494 } else {
495 // From somewhere else
496 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
497 }
498 }
499
500 static void checkItemInfoLocked(
501 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
502 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
503 if (modelItem != null && item != modelItem) {
504 // check all the data is consistent
505 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
506 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
507 ShortcutInfo shortcut = (ShortcutInfo) item;
508 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
509 modelShortcut.intent.filterEquals(shortcut.intent) &&
510 modelShortcut.id == shortcut.id &&
511 modelShortcut.itemType == shortcut.itemType &&
512 modelShortcut.container == shortcut.container &&
513 modelShortcut.screenId == shortcut.screenId &&
514 modelShortcut.cellX == shortcut.cellX &&
515 modelShortcut.cellY == shortcut.cellY &&
516 modelShortcut.spanX == shortcut.spanX &&
517 modelShortcut.spanY == shortcut.spanY &&
518 ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
519 (modelShortcut.dropPos != null &&
520 shortcut.dropPos != null &&
521 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
522 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
523 // For all intents and purposes, this is the same object
524 return;
525 }
526 }
527
528 // the modelItem needs to match up perfectly with item if our model is
529 // to be consistent with the database-- for now, just require
530 // modelItem == item or the equality check above
531 String msg = "item: " + ((item != null) ? item.toString() : "null") +
532 "modelItem: " +
533 ((modelItem != null) ? modelItem.toString() : "null") +
534 "Error: ItemInfo passed to checkItemInfo doesn't match original";
535 RuntimeException e = new RuntimeException(msg);
536 if (stackTrace != null) {
537 e.setStackTrace(stackTrace);
538 }
539 throw e;
540 }
541 }
542
543 static void checkItemInfo(final ItemInfo item) {
544 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
545 final long itemId = item.id;
546 Runnable r = new Runnable() {
547 public void run() {
548 synchronized (sBgLock) {
549 checkItemInfoLocked(itemId, item, stackTrace);
550 }
551 }
552 };
553 runOnWorkerThread(r);
554 }
555
556 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
557 final ItemInfo item, final String callingFunction) {
558 final long itemId = item.id;
559 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
560 final ContentResolver cr = context.getContentResolver();
561
562 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
563 Runnable r = new Runnable() {
564 public void run() {
565 cr.update(uri, values, null, null);
566 updateItemArrays(item, itemId, stackTrace);
567 }
568 };
569 runOnWorkerThread(r);
570 }
571
572 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
573 final ArrayList<ItemInfo> items, final String callingFunction) {
574 final ContentResolver cr = context.getContentResolver();
575
576 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
577 Runnable r = new Runnable() {
578 public void run() {
579 ArrayList<ContentProviderOperation> ops =
580 new ArrayList<ContentProviderOperation>();
581 int count = items.size();
582 for (int i = 0; i < count; i++) {
583 ItemInfo item = items.get(i);
584 final long itemId = item.id;
585 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId, false);
586 ContentValues values = valuesList.get(i);
587
588 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
589 updateItemArrays(item, itemId, stackTrace);
590
591 }
592 try {
593 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
594 } catch (Exception e) {
595 e.printStackTrace();
596 }
597 }
598 };
599 runOnWorkerThread(r);
600 }
601
602 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
603 // Lock on mBgLock *after* the db operation
604 synchronized (sBgLock) {
605 checkItemInfoLocked(itemId, item, stackTrace);
606
607 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
608 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
609 // Item is in a folder, make sure this folder exists
610 if (!sBgFolders.containsKey(item.container)) {
611 // An items container is being set to a that of an item which is not in
612 // the list of Folders.
613 String msg = "item: " + item + " container being set to: " +
614 item.container + ", not in the list of folders";
615 Log.e(TAG, msg);
616 }
617 }
618
619 // Items are added/removed from the corresponding FolderInfo elsewhere, such
620 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
621 // that are on the desktop, as appropriate
622 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
623 if (modelItem != null &&
624 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
625 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
626 switch (modelItem.itemType) {
627 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
628 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
629 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
630 if (!sBgWorkspaceItems.contains(modelItem)) {
631 sBgWorkspaceItems.add(modelItem);
632 }
633 break;
634 default:
635 break;
636 }
637 } else {
638 sBgWorkspaceItems.remove(modelItem);
639 }
640 }
641 }
642
643 public void flushWorkerThread() {
644 mFlushingWorkerThread = true;
645 Runnable waiter = new Runnable() {
646 public void run() {
647 synchronized (this) {
648 notifyAll();
649 mFlushingWorkerThread = false;
650 }
651 }
652 };
653
654 synchronized(waiter) {
655 runOnWorkerThread(waiter);
656 if (mLoaderTask != null) {
657 synchronized(mLoaderTask) {
658 mLoaderTask.notify();
659 }
660 }
661 boolean success = false;
662 while (!success) {
663 try {
664 waiter.wait();
665 success = true;
666 } catch (InterruptedException e) {
667 }
668 }
669 }
670 }
671
672 /**
673 * Move an item in the DB to a new <container, screen, cellX, cellY>
674 */
675 static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
676 final long screenId, final int cellX, final int cellY) {
677 item.container = container;
678 item.cellX = cellX;
679 item.cellY = cellY;
680
681 // We store hotseat items in canonical form which is this orientation invariant position
682 // in the hotseat
683 if (context instanceof Launcher && screenId < 0 &&
684 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
685 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
686 } else {
687 item.screenId = screenId;
688 }
689
690 final ContentValues values = new ContentValues();
691 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
692 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
693 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
694 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
695
696 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
697 }
698
699 /**
700 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
701 * cellX, cellY have already been updated on the ItemInfos.
702 */
703 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
704 final long container, final int screen) {
705
706 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
707 int count = items.size();
708
709 for (int i = 0; i < count; i++) {
710 ItemInfo item = items.get(i);
711 item.container = container;
712
713 // We store hotseat items in canonical form which is this orientation invariant position
714 // in the hotseat
715 if (context instanceof Launcher && screen < 0 &&
716 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
717 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
718 item.cellY);
719 } else {
720 item.screenId = screen;
721 }
722
723 final ContentValues values = new ContentValues();
724 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
725 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
726 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
727 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
728
729 contentValues.add(values);
730 }
731 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
732 }
733
734 /**
735 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
736 */
737 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
738 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
739 item.container = container;
740 item.cellX = cellX;
741 item.cellY = cellY;
742 item.spanX = spanX;
743 item.spanY = spanY;
744
745 // We store hotseat items in canonical form which is this orientation invariant position
746 // in the hotseat
747 if (context instanceof Launcher && screenId < 0 &&
748 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
749 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
750 } else {
751 item.screenId = screenId;
752 }
753
754 final ContentValues values = new ContentValues();
755 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
756 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
757 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
758 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
759 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
760 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
761
762 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
763 }
764
765 /**
766 * Update an item to the database in a specified container.
767 */
768 static void updateItemInDatabase(Context context, final ItemInfo item) {
769 final ContentValues values = new ContentValues();
770 item.onAddToDatabase(values);
771 item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
772 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
773 }
774
775 /**
776 * Returns true if the shortcuts already exists in the database.
777 * we identify a shortcut by its title and intent.
778 */
779 static boolean shortcutExists(Context context, String title, Intent intent) {
780 final ContentResolver cr = context.getContentResolver();
781 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
782 new String[] { "title", "intent" }, "title=? and intent=?",
783 new String[] { title, intent.toUri(0) }, null);
784 boolean result = false;
785 try {
786 result = c.moveToFirst();
787 } finally {
788 c.close();
789 }
790 return result;
791 }
792
793 /**
794 * Returns true if the shortcuts already exists in the database.
795 * we identify a shortcut by the component name of the intent.
796 */
797 static boolean appWasRestored(Context context, Intent intent) {
798 final ContentResolver cr = context.getContentResolver();
799 final ComponentName component = intent.getComponent();
800 if (component == null) {
801 return false;
802 }
803 String componentName = component.flattenToString();
804 final String where = "intent glob \"*component=" + componentName + "*\" and restored = 1";
805 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
806 new String[]{"intent", "restored"}, where, null, null);
807 boolean result = false;
808 try {
809 result = c.moveToFirst();
810 } finally {
811 c.close();
812 }
813 Log.d(TAG, "shortcutWasRestored is " + result + " for " + componentName);
814 return result;
815 }
816
817 /**
818 * Returns an ItemInfo array containing all the items in the LauncherModel.
819 * The ItemInfo.id is not set through this function.
820 */
821 static ArrayList<ItemInfo> getItemsInLocalCoordinates(Context context) {
822 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
823 final ContentResolver cr = context.getContentResolver();
824 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] {
825 LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER,
826 LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Fav🔵
827 LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null);
828
829 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
830 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
831 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
832 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
833 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
834 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
835 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
836
837 try {
838 while (c.moveToNext()) {
839 ItemInfo item = new ItemInfo();
840 item.cellX = c.getInt(cellXIndex);
841 item.cellY = c.getInt(cellYIndex);
842 item.spanX = Math.max(1, c.getInt(spanXIndex));
843 item.spanY = Math.max(1, c.getInt(spanYIndex));
844 item.container = c.getInt(containerIndex);
845 item.itemType = c.getInt(itemTypeIndex);
846 item.screenId = c.getInt(screenIndex);
847
848 items.add(item);
849 }
850 } catch (Exception e) {
851 items.clear();
852 } finally {
853 c.close();
854 }
855
856 return items;
857 }
858
859 /**
860 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
861 */
862 FolderInfo getFolderById(Context context, HashMap<Long,FolderInfo> folderList, long id) {
863 final ContentResolver cr = context.getContentResolver();
864 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
865 "_id=? and (itemType=? or itemType=?)",
866 new String[] { String.valueOf(id),
867 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
868
869 try {
870 if (c.moveToFirst()) {
871 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
872 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
873 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
874 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
875 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
876 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
877
878 FolderInfo folderInfo = null;
879 switch (c.getInt(itemTypeIndex)) {
880 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
881 folderInfo = findOrMakeFolder(folderList, id);
882 break;
883 }
884
885 folderInfo.title = c.getString(titleIndex);
886 folderInfo.id = id;
887 folderInfo.container = c.getInt(containerIndex);
888 folderInfo.screenId = c.getInt(screenIndex);
889 folderInfo.cellX = c.getInt(cellXIndex);
890 folderInfo.cellY = c.getInt(cellYIndex);
891
892 return folderInfo;
893 }
894 } finally {
895 c.close();
896 }
897
898 return null;
899 }
900
901 /**
902 * Add an item to the database in a specified container. Sets the container, screen, cellX and
903 * cellY fields of the item. Also assigns an ID to the item.
904 */
905 static void addItemToDatabase(Context context, final ItemInfo item, final long container,
906 final long screenId, final int cellX, final int cellY, final boolean notify) {
907 item.container = container;
908 item.cellX = cellX;
909 item.cellY = cellY;
910 // We store hotseat items in canonical form which is this orientation invariant position
911 // in the hotseat
912 if (context instanceof Launcher && screenId < 0 &&
913 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
914 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
915 } else {
916 item.screenId = screenId;
917 }
918
919 final ContentValues values = new ContentValues();
920 final ContentResolver cr = context.getContentResolver();
921 item.onAddToDatabase(values);
922
923 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
924 values.put(LauncherSettings.Favorites._ID, item.id);
925 item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
926
927 Runnable r = new Runnable() {
928 public void run() {
929 cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI :
930 LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION, values);
931
932 // Lock on mBgLock *after* the db operation
933 synchronized (sBgLock) {
934 checkItemInfoLocked(item.id, item, null);
935 sBgItemsIdMap.put(item.id, item);
936 switch (item.itemType) {
937 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
938 sBgFolders.put(item.id, (FolderInfo) item);
939 // Fall through
940 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
941 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
942 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
943 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
944 sBgWorkspaceItems.add(item);
945 } else {
946 if (!sBgFolders.containsKey(item.container)) {
947 // Adding an item to a folder that doesn't exist.
948 String msg = "adding item: " + item + " to a folder that " +
949 " doesn't exist";
950 Log.e(TAG, msg);
951 }
952 }
953 break;
954 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
955 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
956 break;
957 }
958 }
959 }
960 };
961 runOnWorkerThread(r);
962 }
963
964 /**
965 * Creates a new unique child id, for a given cell span across all layouts.
966 */
967 static int getCellLayoutChildId(
968 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
969 return (((int) container & 0xFF) << 24)
970 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
971 }
972
973 /**
974 * Removes the specified item from the database
975 * @param context
976 * @param item
977 */
978 static void deleteItemFromDatabase(Context context, final ItemInfo item) {
979 final ContentResolver cr = context.getContentResolver();
980 final Uri uriToDelete = LauncherSettings.Favorites.getContentUri(item.id, false);
981
982 Runnable r = new Runnable() {
983 public void run() {
984 cr.delete(uriToDelete, null, null);
985
986 // Lock on mBgLock *after* the db operation
987 synchronized (sBgLock) {
988 switch (item.itemType) {
989 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
990 sBgFolders.remove(item.id);
991 for (ItemInfo info: sBgItemsIdMap.values()) {
992 if (info.container == item.id) {
993 // We are deleting a folder which still contains items that
994 // think they are contained by that folder.
995 String msg = "deleting a folder (" + item + ") which still " +
996 "contains items (" + info + ")";
997 Log.e(TAG, msg);
998 }
999 }
1000 sBgWorkspaceItems.remove(item);
1001 break;
1002 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1003 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1004 sBgWorkspaceItems.remove(item);
1005 break;
1006 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1007 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
1008 break;
1009 }
1010 sBgItemsIdMap.remove(item.id);
1011 sBgDbIconCache.remove(item);
1012 }
1013 }
1014 };
1015 runOnWorkerThread(r);
1016 }
1017
1018 /**
1019 * Update the order of the workspace screens in the database. The array list contains
1020 * a list of screen ids in the order that they should appear.
1021 */
1022 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1023 // Log to disk
1024 Launcher.addDumpLog(TAG, "11683562 - updateWorkspaceScreenOrder()", true);
1025 Launcher.addDumpLog(TAG, "11683562 - screens: " + TextUtils.join(", ", screens), true);
1026
1027 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1028 final ContentResolver cr = context.getContentResolver();
1029 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1030
1031 // Remove any negative screen ids -- these aren't persisted
1032 Iterator<Long> iter = screensCopy.iterator();
1033 while (iter.hasNext()) {
1034 long id = iter.next();
1035 if (id < 0) {
1036 iter.remove();
1037 }
1038 }
1039
1040 Runnable r = new Runnable() {
1041 @Override
1042 public void run() {
1043 // Clear the table
1044 cr.delete(uri, null, null);
1045 int count = screensCopy.size();
1046 ContentValues[] values = new ContentValues[count];
1047 for (int i = 0; i < count; i++) {
1048 ContentValues v = new ContentValues();
1049 long screenId = screensCopy.get(i);
1050 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1051 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1052 values[i] = v;
1053 }
1054 cr.bulkInsert(uri, values);
1055
1056 synchronized (sBgLock) {
1057 sBgWorkspaceScreens.clear();
1058 sBgWorkspaceScreens.addAll(screensCopy);
1059 }
1060 }
1061 };
1062 runOnWorkerThread(r);
1063 }
1064
1065 /**
1066 * Remove the contents of the specified folder from the database
1067 */
1068 static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1069 final ContentResolver cr = context.getContentResolver();
1070
1071 Runnable r = new Runnable() {
1072 public void run() {
1073 cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false), null, null);
1074 // Lock on mBgLock *after* the db operation
1075 synchronized (sBgLock) {
1076 sBgItemsIdMap.remove(info.id);
1077 sBgFolders.remove(info.id);
1078 sBgDbIconCache.remove(info);
1079 sBgWorkspaceItems.remove(info);
1080 }
1081
1082 cr.delete(LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
1083 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1084 // Lock on mBgLock *after* the db operation
1085 synchronized (sBgLock) {
1086 for (ItemInfo childInfo : info.contents) {
1087 sBgItemsIdMap.remove(childInfo.id);
1088 sBgDbIconCache.remove(childInfo);
1089 }
1090 }
1091 }
1092 };
1093 runOnWorkerThread(r);
1094 }
1095
1096 /**
1097 * Set this as the current Launcher activity object for the loader.
1098 */
1099 public void initialize(Callbacks callbacks) {
1100 synchronized (mLock) {
1101 mCallbacks = new WeakReference<Callbacks>(callbacks);
1102 }
1103 }
1104
1105 /**
1106 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1107 * ACTION_PACKAGE_CHANGED.
1108 */
1109 @Override
1110 public void onReceive(Context context, Intent intent) {
1111 if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
1112
1113 final String action = intent.getAction();
1114
1115 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
1116 || Intent.ACTION_PACKAGE_REMOVED.equals(action)
1117 || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
1118 final String packageName = intent.getData().getSchemeSpecificPart();
1119 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1120
1121 int op = PackageUpdatedTask.OP_NONE;
1122
1123 if (packageName == null || packageName.length() == 0) {
1124 // they sent us a bad intent
1125 return;
1126 }
1127
1128 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
1129 op = PackageUpdatedTask.OP_UPDATE;
1130 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
1131 if (!replacing) {
1132 op = PackageUpdatedTask.OP_REMOVE;
1133 }
1134 // else, we are replacing the package, so a PACKAGE_ADDED will be sent
1135 // later, we will update the package at this time
1136 } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
1137 if (!replacing) {
1138 op = PackageUpdatedTask.OP_ADD;
1139 } else {
1140 op = PackageUpdatedTask.OP_UPDATE;
1141 }
1142 }
1143
1144 if (op != PackageUpdatedTask.OP_NONE) {
1145 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName }));
1146 }
1147
1148 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
1149 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1150 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1151 if (!replacing) {
1152 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
1153 if (mAppsCanBeOnRemoveableStorage) {
1154 // Only rebind if we support removable storage. It catches the case where
1155 // apps on the external sd card need to be reloaded
1156 startLoaderFromBackground();
1157 }
1158 } else {
1159 // If we are replacing then just update the packages in the list
1160 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1161 packages));
1162 }
1163 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
1164 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1165 if (!replacing) {
1166 String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1167 enqueuePackageUpdated(new PackageUpdatedTask(
1168 PackageUpdatedTask.OP_UNAVAILABLE, packages));
1169 }
1170 // else, we are replacing the packages, so ignore this event and wait for
1171 // EXTERNAL_APPLICATIONS_AVAILABLE to update the packages at that time
1172 } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1173 // If we have changed locale we need to clear out the labels in all apps/workspace.
1174 forceReload();
1175 } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
1176 // Check if configuration change was an mcc/mnc change which would affect app resources
1177 // and we would need to clear out the labels in all apps/workspace. Same handling as
1178 // above for ACTION_LOCALE_CHANGED
1179 Configuration currentConfig = context.getResources().getConfiguration();
1180 if (mPreviousConfigMcc != currentConfig.mcc) {
1181 Log.d(TAG, "Reload apps on config change. curr_mcc:"
1182 + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc);
1183 forceReload();
1184 }
1185 // Update previousConfig
1186 mPreviousConfigMcc = currentConfig.mcc;
1187 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
1188 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
1189 if (mCallbacks != null) {
1190 Callbacks callbacks = mCallbacks.get();
1191 if (callbacks != null) {
1192 callbacks.bindSearchablesChanged();
1193 }
1194 }
1195 }
1196 }
1197
1198 private void forceReload() {
1199 resetLoadedState(true, true);
1200
1201 // Do this here because if the launcher activity is running it will be restarted.
1202 // If it's not running startLoaderFromBackground will merely tell it that it needs
1203 // to reload.
1204 startLoaderFromBackground();
1205 }
1206
1207 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1208 synchronized (mLock) {
1209 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1210 // mWorkspaceLoaded to true later
1211 stopLoaderLocked();
1212 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1213 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1214 }
1215 }
1216
1217 /**
1218 * When the launcher is in the background, it's possible for it to miss paired
1219 * configuration changes. So whenever we trigger the loader from the background
1220 * tell the launcher that it needs to re-run the loader when it comes back instead
1221 * of doing it now.
1222 */
1223 public void startLoaderFromBackground() {
1224 boolean runLoader = false;
1225 if (mCallbacks != null) {
1226 Callbacks callbacks = mCallbacks.get();
1227 if (callbacks != null) {
1228 // Only actually run the loader if they're not paused.
1229 if (!callbacks.setLoadOnResume()) {
1230 runLoader = true;
1231 }
1232 }
1233 }
1234 if (runLoader) {
1235 startLoader(false, PagedView.INVALID_RESTORE_PAGE);
1236 }
1237 }
1238
1239 // If there is already a loader task running, tell it to stop.
1240 // returns true if isLaunching() was true on the old task
1241 private boolean stopLoaderLocked() {
1242 boolean isLaunching = false;
1243 LoaderTask oldTask = mLoaderTask;
1244 if (oldTask != null) {
1245 if (oldTask.isLaunching()) {
1246 isLaunching = true;
1247 }
1248 oldTask.stopLocked();
1249 }
1250 return isLaunching;
1251 }
1252
1253 public void startLoader(boolean isLaunching, int synchronousBindPage) {
1254 startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE);
1255 }
1256
1257 public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) {
1258 synchronized (mLock) {
1259 if (DEBUG_LOADERS) {
1260 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
1261 }
1262
1263 // Clear any deferred bind-runnables from the synchronized load process
1264 // We must do this before any loading/binding is scheduled below.
1265 mDeferredBindRunnables.clear();
1266
1267 // Don't bother to start the thread if we know it's not going to do anything
1268 if (mCallbacks != null && mCallbacks.get() != null) {
1269 // If there is already one running, tell it to stop.
1270 // also, don't downgrade isLaunching if we're already running
1271 isLaunching = isLaunching || stopLoaderLocked();
1272 mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags);
1273 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
1274 && mAllAppsLoaded && mWorkspaceLoaded) {
1275 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1276 } else {
1277 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1278 sWorker.post(mLoaderTask);
1279 }
1280 }
1281 }
1282 }
1283
1284 void bindRemainingSynchronousPages() {
1285 // Post the remaining side pages to be loaded
1286 if (!mDeferredBindRunnables.isEmpty()) {
1287 for (final Runnable r : mDeferredBindRunnables) {
1288 mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
1289 }
1290 mDeferredBindRunnables.clear();
1291 }
1292 }
1293
1294 public void stopLoader() {
1295 synchronized (mLock) {
1296 if (mLoaderTask != null) {
1297 mLoaderTask.stopLocked();
1298 }
1299 }
1300 }
1301
1302 /** Loads the workspace screens db into a map of Rank -> ScreenId */
1303 private static TreeMap<Integer, Long> loadWorkspaceScreensDb(Context context) {
1304 final ContentResolver contentResolver = context.getContentResolver();
1305 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1306 final Cursor sc = contentResolver.query(screensUri, null, null, null, null);
1307 TreeMap<Integer, Long> orderedScreens = new TreeMap<Integer, Long>();
1308
1309 try {
1310 final int idIndex = sc.getColumnIndexOrThrow(
1311 LauncherSettings.WorkspaceScreens._ID);
1312 final int rankIndex = sc.getColumnIndexOrThrow(
1313 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
1314 while (sc.moveToNext()) {
1315 try {
1316 long screenId = sc.getLong(idIndex);
1317 int rank = sc.getInt(rankIndex);
1318 orderedScreens.put(rank, screenId);
1319 } catch (Exception e) {
1320 Launcher.addDumpLog(TAG, "Desktop items loading interrupted - invalid screens: " + e,🔵
1321 }
1322 }
1323 } finally {
1324 sc.close();
1325 }
1326
1327 // Log to disk
1328 Launcher.addDumpLog(TAG, "11683562 - loadWorkspaceScreensDb()", true);
1329 ArrayList<String> orderedScreensPairs= new ArrayList<String>();
1330 for (Integer i : orderedScreens.keySet()) {
1331 orderedScreensPairs.add("{ " + i + ": " + orderedScreens.get(i) + " }");
1332 }
1333 Launcher.addDumpLog(TAG, "11683562 - screens: " +
1334 TextUtils.join(", ", orderedScreensPairs), true);
1335 return orderedScreens;
1336 }
1337
1338 public boolean isAllAppsLoaded() {
1339 return mAllAppsLoaded;
1340 }
1341
1342 boolean isLoadingWorkspace() {
1343 synchronized (mLock) {
1344 if (mLoaderTask != null) {
1345 return mLoaderTask.isLoadingWorkspace();
1346 }
1347 }
1348 return false;
1349 }
1350
1351 /**
1352 * Runnable for the thread that loads the contents of the launcher:
1353 * - workspace icons
1354 * - widgets
1355 * - all apps icons
1356 */
1357 private class LoaderTask implements Runnable {
1358 private Context mContext;
1359
1360 private boolean mIsLaunching;
1361
1362 private boolean mIsLoadingAndBindingWorkspace;
1363
1364 private boolean mStopped;
1365
1366 private boolean mLoadAndBindStepFinished;
1367
1368 private int mFlags;
1369
1370 private HashMap<Object, CharSequence> mLabelCache;
1371
1372 LoaderTask(Context context, boolean isLaunching, int flags) {
1373 mContext = context;
1374 mIsLaunching = isLaunching;
1375 mLabelCache = new HashMap<Object, CharSequence>();
1376 mFlags = flags;
1377 }
1378
1379 boolean isLaunching() {
1380 return mIsLaunching;
1381 }
1382
1383 boolean isLoadingWorkspace() {
1384 return mIsLoadingAndBindingWorkspace;
1385 }
1386
1387 /** Returns whether this is an upgrade path */
1388 private boolean loadAndBindWorkspace() {
1389 mIsLoadingAndBindingWorkspace = true;
1390
1391 // Load the workspace
1392 if (DEBUG_LOADERS) {
1393 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1394 }
1395
1396 boolean isUpgradePath = false;
1397 if (!mWorkspaceLoaded) {
1398 isUpgradePath = loadWorkspace();
1399 synchronized (LoaderTask.this) {
1400 if (mStopped) {
1401 return isUpgradePath;
1402 }
1403 mWorkspaceLoaded = true;
1404 }
1405 }
1406
1407 // Bind the workspace
1408 bindWorkspace(-1, isUpgradePath);
1409 return isUpgradePath;
1410 }
1411
1412 private void waitForIdle() {
1413 // Wait until the either we're stopped or the other threads are done.
1414 // This way we don't start loading all apps until the workspace has settled
1415 // down.
1416 synchronized (LoaderTask.this) {
1417 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1418
1419 mHandler.postIdle(new Runnable() {
1420 public void run() {
1421 synchronized (LoaderTask.this) {
1422 mLoadAndBindStepFinished = true;
1423 if (DEBUG_LOADERS) {
1424 Log.d(TAG, "done with previous binding step");
1425 }
1426 LoaderTask.this.notify();
1427 }
1428 }
1429 });
1430
1431 while (!mStopped && !mLoadAndBindStepFinished && !mFlushingWorkerThread) {
1432 try {
1433 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1434 // wait no longer than 1sec at a time
1435 this.wait(1000);
1436 } catch (InterruptedException ex) {
1437 // Ignore
1438 }
1439 }
1440 if (DEBUG_LOADERS) {
1441 Log.d(TAG, "waited "
1442 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1443 + "ms for previous step to finish binding");
1444 }
1445 }
1446 }
1447
1448 void runBindSynchronousPage(int synchronousBindPage) {
1449 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1450 // Ensure that we have a valid page index to load synchronously
1451 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1452 "valid page index");
1453 }
1454 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1455 // Ensure that we don't try and bind a specified page when the pages have not been
1456 // loaded already (we should load everything asynchronously in that case)
1457 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1458 }
1459 synchronized (mLock) {
1460 if (mIsLoaderTaskRunning) {
1461 // Ensure that we are never running the background loading at this point since
1462 // we also touch the background collections
1463 throw new RuntimeException("Error! Background loading is already running");
1464 }
1465 }
1466
1467 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1468 // data structures, we can't allow any other thread to touch that data, but because
1469 // this call is synchronous, we can get away with not locking).
1470
1471 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1472 // operations from the previous activity. We need to ensure that all queued operations
1473 // are executed before any synchronous binding work is done.
1474 mHandler.flush();
1475
1476 // Divide the set of loaded items into those that we are binding synchronously, and
1477 // everything else that is to be bound normally (asynchronously).
1478 bindWorkspace(synchronousBindPage, false);
1479 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1480 // arise from that.
1481 onlyBindAllApps();
1482 }
1483
1484 public void run() {
1485 boolean isUpgrade = false;
1486
1487 synchronized (mLock) {
1488 mIsLoaderTaskRunning = true;
1489 }
1490 // Optimize for end-user experience: if the Launcher is up and // running with the
1491 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1492 // workspace first (default).
1493 keep_running: {
1494 // Elevate priority when Home launches for the first time to avoid
1495 // starving at boot time. Staring at a blank home is not cool.
1496 synchronized (mLock) {
1497 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
1498 (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
1499 android.os.Process.setThreadPriority(mIsLaunching
1500 ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
1501 }
1502 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1503 isUpgrade = loadAndBindWorkspace();
1504
1505 if (mStopped) {
1506 break keep_running;
1507 }
1508
1509 // Whew! Hard work done. Slow us down, and wait until the UI thread has
1510 // settled down.
1511 synchronized (mLock) {
1512 if (mIsLaunching) {
1513 if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
1514 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
1515 }
1516 }
1517 waitForIdle();
1518
1519 // second step
1520 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1521 loadAndBindAllApps();
1522
1523 // Restore the default thread priority after we are done loading items
1524 synchronized (mLock) {
1525 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
1526 }
1527 }
1528
1529 // Update the saved icons if necessary
1530 if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
1531 synchronized (sBgLock) {
1532 for (Object key : sBgDbIconCache.keySet()) {
1533 updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
1534 }
1535 sBgDbIconCache.clear();
1536 }
1537
1538 if (LauncherAppState.isDisableAllApps()) {
1539 // Ensure that all the applications that are in the system are
1540 // represented on the home screen.
1541 if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) {
1542 verifyApplications();
1543 }
1544 }
1545
1546 // Clear out this reference, otherwise we end up holding it until all of the
1547 // callback runnables are done.
1548 mContext = null;
1549
1550 synchronized (mLock) {
1551 // If we are still the last one to be scheduled, remove ourselves.
1552 if (mLoaderTask == this) {
1553 mLoaderTask = null;
1554 }
1555 mIsLoaderTaskRunning = false;
1556 }
1557 }
1558
1559 public void stopLocked() {
1560 synchronized (LoaderTask.this) {
1561 mStopped = true;
1562 this.notify();
1563 }
1564 }
1565
1566 /**
1567 * Gets the callbacks object. If we've been stopped, or if the launcher object
1568 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1569 * object that was around when the deferred message was scheduled, and if there's
1570 * a new Callbacks object around then also return null. This will save us from
1571 * calling onto it with data that will be ignored.
1572 */
1573 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1574 synchronized (mLock) {
1575 if (mStopped) {
1576 return null;
1577 }
1578
1579 if (mCallbacks == null) {
1580 return null;
1581 }
1582
1583 final Callbacks callbacks = mCallbacks.get();
1584 if (callbacks != oldCallbacks) {
1585 return null;
1586 }
1587 if (callbacks == null) {
1588 Log.w(TAG, "no mCallbacks");
1589 return null;
1590 }
1591
1592 return callbacks;
1593 }
1594 }
1595
1596 private void verifyApplications() {
1597 final Context context = mApp.getContext();
1598 // Cross reference all the applications in our apps list with items in the workspace
1599 ArrayList<ItemInfo> tmpInfos;
1600 ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
1601 synchronized(sBgLock) {
1602 for (AppInfo app : mBgAllAppsList.data) {
1603 tmpInfos = getItemInfoForComponentName(app.componentName);
1604 if (tmpInfos.isEmpty()) {
1605 // We are missing an application icon, so add this to the workspace
1606 added.add(app);
1607 // This is a rare event, so lets log it
1608 Log.e(TAG, "Missing Application on load: " + app);
1609 }
1610 }
1611 }
1612 if (!added.isEmpty()) {
1613 Callbacks cb = (mCallbacks != null) ? mCallbacks.get() : null;
1614 addAndBindAddedApps(context, added, cb, new ArrayList<AppInfo>());
1615 }
1616 }
1617
1618 // check & update map of what's occupied; used to discard overlapping/invalid items
1619 private boolean checkItemPlacement(HashMap<Long, ItemInfo[][]> occupied, ItemInfo item,
1620 AtomicBoolean deleteOnInvalidPlacement) {
1621 LauncherAppState app = LauncherAppState.getInstance();
1622 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1623 final int countX = (int) grid.numColumns;
1624 final int countY = (int) grid.numRows;
1625
1626 long containerIndex = item.screenId;
1627 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1628 // Return early if we detect that an item is under the hotseat button
1629 if (mCallbacks == null ||
1630 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
1631 deleteOnInvalidPlacement.set(true);
1632 Log.e(TAG, "Error loading shortcut into hotseat " + item
1633 + " into position (" + item.screenId + ":" + item.cellX + ","
1634 + item.cellY + ") occupied by all apps");
1635 return false;
1636 }
1637
1638 final ItemInfo[][] hotseatItems =
1639 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1640
1641 if (item.screenId >= grid.numHotseatIcons) {
1642 Log.e(TAG, "Error loading shortcut " + item
1643 + " into hotseat position " + item.screenId
1644 + ", position out of bounds: (0 to " + (grid.numHotseatIcons - 1)
1645 + ")");
1646 return false;
1647 }
1648
1649 if (hotseatItems != null) {
1650 if (hotseatItems[(int) item.screenId][0] != null) {
1651 Log.e(TAG, "Error loading shortcut into hotseat " + item
1652 + " into position (" + item.screenId + ":" + item.cellX + ","
1653 + item.cellY + ") occupied by "
1654 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
1655 [(int) item.screenId][0]);
1656 return false;
1657 } else {
1658 hotseatItems[(int) item.screenId][0] = item;
1659 return true;
1660 }
1661 } else {
1662 final ItemInfo[][] items = new ItemInfo[(int) grid.numHotseatIcons][1];
1663 items[(int) item.screenId][0] = item;
1664 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
1665 return true;
1666 }
1667 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1668 // Skip further checking if it is not the hotseat or workspace container
1669 return true;
1670 }
1671
1672 if (!occupied.containsKey(item.screenId)) {
1673 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1674 occupied.put(item.screenId, items);
1675 }
1676
1677 final ItemInfo[][] screens = occupied.get(item.screenId);
1678 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1679 item.cellX < 0 || item.cellY < 0 ||
1680 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1681 Log.e(TAG, "Error loading shortcut " + item
1682 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1683 + item.cellX + "," + item.cellY
1684 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1685 return false;
1686 }
1687
1688 // Check if any workspace icons overlap with each other
1689 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1690 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1691 if (screens[x][y] != null) {
1692 Log.e(TAG, "Error loading shortcut " + item
1693 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1694 + x + "," + y
1695 + ") occupied by "
1696 + screens[x][y]);
1697 return false;
1698 }
1699 }
1700 }
1701 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1702 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1703 screens[x][y] = item;
1704 }
1705 }
1706
1707 return true;
1708 }
1709
1710 /** Clears all the sBg data structures */
1711 private void clearSBgDataStructures() {
1712 synchronized (sBgLock) {
1713 sBgWorkspaceItems.clear();
1714 sBgAppWidgets.clear();
1715 sBgFolders.clear();
1716 sBgItemsIdMap.clear();
1717 sBgDbIconCache.clear();
1718 sBgWorkspaceScreens.clear();
1719 }
1720 }
1721
1722 /** Returns whether this is an upgrade path */
1723 private boolean loadWorkspace() {
1724 // Log to disk
1725 Launcher.addDumpLog(TAG, "11683562 - loadWorkspace()", true);
1726 final long t = (DEBUG_LOADERS) ? SystemClock.uptimeMillis() : 0;
1727 final Context context = mContext;
1728 final ContentResolver contentResolver = context.getContentResolver();
1729 final PackageManager manager = context.getPackageManager();
1730 final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
1731 final boolean isSafeMode = manager.isSafeMode();
1732 LauncherAppState app = LauncherAppState.getInstance();
1733 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
1734 int countX = ((int) (grid.numColumns));
1735 int countY = ((int) (grid.numRows));
1736 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1737 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1738 LauncherAppState.getLauncherProvider().deleteDatabase();
1739 }
1740 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1741 // append the user's Launcher2 shortcuts
1742 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1743 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1744 } else {
1745 // Make sure the default workspace is loaded
1746 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1747 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
1748 }
1749 // Check if we need to do any upgrade-path logic
1750 // (Includes having just imported default favorites)
1751 boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb();
1752 // Log to disk
1753 Launcher.addDumpLog(TAG, "11683562 - loadedOldDb: " + loadedOldDb, true);
1754 synchronized(sBgLock) {
1755 clearSBgDataStructures();
1756 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1757 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1758 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1759 if (DEBUG_LOADERS) {
1760 Log.d(TAG, "loading model from " + contentUri);
1761 }
1762 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1763 // +1 for the hotseat (it can be larger than the workspace)
1764 // Load workspace in reverse order to ensure that latest items are loaded first (and
1765 // before any earlier duplicates)
1766 final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>();
1767 try {
1768 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1769 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1770 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1771 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYP🔵
1772 final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
1773 final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_🔵
1774 final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON🔵
1775 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAIN🔵
1776 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYP🔵
1777 final int appWidgetIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.APPWI🔵
1778 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites🔵
1779 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1780 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1781 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1782 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
1783 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
1784 final int restoredIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.RESTORED🔵
1785 // final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
1786 // final int displayModeIndex = c.getColumnIndexOrThrow(
1787 // LauncherSettings.Favorites.DISPLAY_MODE);
1788 ShortcutInfo info;
1789 String intentDescription;
1790 LauncherAppWidgetInfo appWidgetInfo;
1791 int container;
1792 long id;
1793 Intent intent;
1794 while ((!mStopped) && c.moveToNext()) {
1795 AtomicBoolean deleteOnInvalidPlacement = new AtomicBoolean(false);
1796 try {
1797 int itemType = c.getInt(itemTypeIndex);
1798 boolean restored = 0 != c.getInt(restoredIndex);
1799 switch (itemType) {
1800 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION :
1801 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
1802 id = c.getLong(idIndex);
1803 intentDescription = c.getString(intentIndex);
1804 try {
1805 intent = Intent.parseUri(intentDescription, 0);
1806 ComponentName cn = intent.getComponent();
1807 if ((cn != null) && (!isValidPackageComponent(manager, cn))) {
1808 if (restored) {
1809 // might be installed later
1810 Launcher.addDumpLog(TAG, "package not yet restored: " + c🔵
1811 } else {
1812 if (!mAppsCanBeOnRemoveableStorage) {
1813 // Log the invalid package, and remove it
1814 Launcher.addDumpLog(TAG, "Invalid package removed: " 🔵
1815 itemsToRemove.add(id);
1816 } else {
1817 // If apps can be on external storage, then we just
1818 // leave them for the user to remove (maybe add
1819 // visual treatment to it)
1820 Launcher.addDumpLog(TAG, "Invalid package found: " + 🔵
1821 }
1822 continue;
1823 }
1824 } else if (restored) {
1825 // no special handling necessary for this restored item
1826 restoredRows.add(id);
1827 restored = false;
1828 }
1829 } catch (URISyntaxException e) {
1830 Launcher.addDumpLog(TAG, "Invalid uri: " + intentDescription, tru🔵
1831 continue;
1832 }
1833 if (restored) {
1834 Launcher.addDumpLog(TAG, "constructing info for partially restore🔵
1835 info = getRestoredItemInfo(c, titleIndex, intent);
1836 intent = getRestoredItemIntent(c, context, intent);
1837 } else if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATI🔵
1838 info = getShortcutInfo(manager, intent, context, c, iconIndex, ti🔵
1839 } else {
1840 info = getShortcutInfo(c, context, iconTypeIndex, iconPackageInde🔵
1841 // App shortcuts that used to be automatically added to Launcher
1842 // didn't always have the correct intent flags set, so do that
1843 // here
1844 if ((((intent.getAction() != null) && (intent.getCategories() != 🔵
1845 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_A🔵
1846 }
1847 }
1848 if (info != null) {
1849 info.id = id;
1850 info.intent = intent;
1851 container = c.getInt(containerIndex);
1852 info.container = container;
1853 info.screenId = c.getInt(screenIndex);
1854 info.cellX = c.getInt(cellXIndex);
1855 info.cellY = c.getInt(cellYIndex);
1856 info.spanX = 1;
1857 info.spanY = 1;
1858 // check & update map of what's occupied
1859 deleteOnInvalidPlacement.set(false);
1860 if (!checkItemPlacement(occupied, info, deleteOnInvalidPlacement)🔵
1861 if (deleteOnInvalidPlacement.get()) {
1862 itemsToRemove.add(id);
1863 }
1864 break;
1865 }
1866 switch (container) {
1867 case LauncherSettings.Favorites.CONTAINER_DESKTOP :
1868 case LauncherSettings.Favorites.CONTAINER_HOTSEAT :
1869 sBgWorkspaceItems.add(info);
1870 break;
1871 default :
1872 // Item is in a user folder
1873 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, cont🔵
1874 folderInfo.add(info);
1875 break;
1876 }
1877 sBgItemsIdMap.put(info.id, info);
1878 // now that we've loaded everthing re-save it with the
1879 // icon in case it disappears somehow.
1880 queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
1881 } else {
1882 throw new RuntimeException("Unexpected null ShortcutInfo");
1883 }
1884 break;
1885 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
1886 id = c.getLong(idIndex);
1887 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
1888 folderInfo.title = c.getString(titleIndex);
1889 folderInfo.id = id;
1890 container = c.getInt(containerIndex);
1891 folderInfo.container = container;
1892 folderInfo.screenId = c.getInt(screenIndex);
1893 folderInfo.cellX = c.getInt(cellXIndex);
1894 folderInfo.cellY = c.getInt(cellYIndex);
1895 folderInfo.spanX = 1;
1896 folderInfo.spanY = 1;
1897 // check & update map of what's occupied
1898 deleteOnInvalidPlacement.set(false);
1899 if (!checkItemPlacement(occupied, folderInfo, deleteOnInvalidPlacemen🔵
1900 if (deleteOnInvalidPlacement.get()) {
1901 itemsToRemove.add(id);
1902 }
1903 break;
1904 }
1905 switch (container) {
1906 case LauncherSettings.Favorites.CONTAINER_DESKTOP :
1907 case LauncherSettings.Favorites.CONTAINER_HOTSEAT :
1908 sBgWorkspaceItems.add(folderInfo);
1909 break;
1910 }
1911 if (restored) {
1912 // no special handling required for restored folders
1913 restoredRows.add(id);
1914 }
1915 sBgItemsIdMap.put(folderInfo.id, folderInfo);
1916 sBgFolders.put(folderInfo.id, folderInfo);
1917 break;
1918 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET :
1919 // Read all Launcher-specific widget details
1920 int appWidgetId = c.getInt(appWidgetIdIndex);
1921 String savedProvider = c.getString(appWidgetProviderIndex);
1922 id = c.getLong(idIndex);
1923 final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(appWi🔵
1924 if ((!isSafeMode) && (((provider == null) || (provider.provider == nu🔵
1925 String log = (("Deleting widget that isn't installed anymore: id=🔵
1926 Log.e(TAG, log);
1927 Launcher.addDumpLog(TAG, log, false);
1928 itemsToRemove.add(id);
1929 } else {
1930 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, provider.p🔵
1931 appWidgetInfo.id = id;
1932 appWidgetInfo.screenId = c.getInt(screenIndex);
1933 appWidgetInfo.cellX = c.getInt(cellXIndex);
1934 appWidgetInfo.cellY = c.getInt(cellYIndex);
1935 appWidgetInfo.spanX = c.getInt(spanXIndex);
1936 appWidgetInfo.spanY = c.getInt(spanYIndex);
1937 int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
1938 appWidgetInfo.minSpanX = minSpan[0];
1939 appWidgetInfo.minSpanY = minSpan[1];
1940 container = c.getInt(containerIndex);
1941 if ((container != LauncherSettings.Favorites.CONTAINER_DESKTOP) &🔵
1942 Log.e(TAG, "Widget found where container != " + "CONTAINER_DE🔵
1943 continue;
1944 }
1945 appWidgetInfo.container = c.getInt(containerIndex);
1946 // check & update map of what's occupied
1947 deleteOnInvalidPlacement.set(false);
1948 if (!checkItemPlacement(occupied, appWidgetInfo, deleteOnInvalidP🔵
1949 if (deleteOnInvalidPlacement.get()) {
1950 itemsToRemove.add(id);
1951 }
1952 break;
1953 }
1954 String providerName = provider.provider.flattenToString();
1955 if (!providerName.equals(savedProvider)) {
1956 ContentValues values = new ContentValues();
1957 values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, pro🔵
1958 String where = BaseColumns._ID + "= ?";
1959 String[] args = new java.lang.String[]{ Integer.toString(c.ge🔵
1960 contentResolver.update(contentUri, values, where, args);
1961 }
1962 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
1963 sBgAppWidgets.add(appWidgetInfo);
1964 }
1965 break;
1966 }
1967 } catch (java.lang.Exception e) {
1968 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
1969 }
1970 }
1971 } finally {
1972 if (c != null) {
1973 c.close();
1974 }
1975 }
1976 // Break early if we've stopped loading
1977 if (mStopped) {
1978 clearSBgDataStructures();
1979 return false;
1980 }
1981 if (itemsToRemove.size() > 0) {
1982 ContentProviderClient client = contentResolver.acquireContentProviderClient(LauncherS🔵
1983 // Remove dead items
1984 for (long id : itemsToRemove) {
1985 if (DEBUG_LOADERS) {
1986 Log.d(TAG, "Removed id = " + id);
1987 }
1988 // Don't notify content observers
1989 try {
1990 client.delete(LauncherSettings.Favorites.getContentUri(id, false), null, null🔵
1991 } catch (RemoteException e) {
1992 Log.w(TAG, "Could not remove id = " + id);
1993 }
1994 }
1995 }
1996 if (restoredRows.size() > 0) {
1997 ContentProviderClient updater = contentResolver.acquireContentProviderClient(Launcher🔵
1998 // Update restored items that no longer require special handling
1999 try {
2000 StringBuilder selectionBuilder = new StringBuilder();
2001 selectionBuilder.append(LauncherSettings.Favorites._ID);
2002 selectionBuilder.append(" IN (");
2003 selectionBuilder.append(TextUtils.join(", ", restoredRows));
2004 selectionBuilder.append(")");
2005 ContentValues values = new ContentValues();
2006 values.put(LauncherSettings.Favorites.RESTORED, 0);
2007 updater.update(LauncherSettings.Favorites.CONTENT_URI, values, selectionBuilder.t🔵
2008 } catch (RemoteException e) {
2009 Log.w(TAG, "Could not update restored rows");
2010 }
2011 }
2012 if (loadedOldDb) {
2013 long maxScreenId = 0;
2014 // If we're importing we use the old screen order.
2015 for (ItemInfo item : sBgItemsIdMap.values()) {
2016 long screenId = item.screenId;
2017 if ((item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) && (!sBgWork🔵
2018 sBgWorkspaceScreens.add(screenId);
2019 if (screenId > maxScreenId) {
2020 maxScreenId = screenId;
2021 }
2022 }
2023 }
2024 Collections.sort(sBgWorkspaceScreens);
2025 // Log to disk
2026 Launcher.addDumpLog(TAG, "11683562 - maxScreenId: " + maxScreenId, true);
2027 Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " + TextUtils.join(", ", 🔵
2028 LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId);
2029 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2030 // Update the max item id after we load an old db
2031 long maxItemId = 0;
2032 // If we're importing we use the old screen order.
2033 for (ItemInfo item : sBgItemsIdMap.values()) {
2034 maxItemId = Math.max(maxItemId, item.id);
2035 }
2036 LauncherAppState.getLauncherProvider().updateMaxItemId(maxItemId);
2037 } else {
2038 TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(mContext);
2039 for (Integer i : orderedScreens.keySet()) {
2040 sBgWorkspaceScreens.add(orderedScreens.get(i));
2041 }
2042 // Log to disk
2043 Launcher.addDumpLog(TAG, "11683562 - sBgWorkspaceScreens: " + TextUtils.join(", ", 🔵
2044 // Remove any empty screens
2045 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2046 for (ItemInfo item : sBgItemsIdMap.values()) {
2047 long screenId = item.screenId;
2048 if ((item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) && unusedScr🔵
2049 unusedScreens.remove(screenId);
2050 }
2051 }
2052 // If there are any empty screens remove them, and update.
2053 if (unusedScreens.size() != 0) {
2054 // Log to disk
2055 Launcher.addDumpLog(TAG, "11683562 - unusedScreens (to be removed): " + TextUti🔵
2056 sBgWorkspaceScreens.removeAll(unusedScreens);
2057 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2058 }
2059 }
2060 if (DEBUG_LOADERS) {
2061 Log.d(TAG, ("loaded workspace in " + (SystemClock.uptimeMillis() - t)) + "ms");
2062 Log.d(TAG, "workspace layout: ");
2063 int nScreens = occupied.size();
2064 for (int y = 0; y < countY; y++) {
2065 String line = "";
2066 Iterator<Long> iter = occupied.keySet().iterator();
2067 while (iter.hasNext()) {
2068 long screenId = iter.next();
2069 if (screenId > 0) {
2070 line += " | ";
2071 }
2072 for (int x = 0; x < countX; x++) {
2073 line += (occupied.get(screenId)[x][y] != null) ? "#" : ".";
2074 }
2075 }
2076 Log.d(TAG, ("[ " + line) + " ]");
2077 }
2078 }
2079 }
2080 return loadedOldDb;
2081 }
2082
2083 /** Filters the set of items who are directly or indirectly (via another container) on the
2084 * specified screen. */
2085 private void filterCurrentWorkspaceItems(long currentScreenId,
2086 ArrayList<ItemInfo> allWorkspaceItems,
2087 ArrayList<ItemInfo> currentScreenItems,
2088 ArrayList<ItemInfo> otherScreenItems) {
2089 // Purge any null ItemInfos
2090 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2091 while (iter.hasNext()) {
2092 ItemInfo i = iter.next();
2093 if (i == null) {
2094 iter.remove();
2095 }
2096 }
2097
2098 // Order the set of items by their containers first, this allows use to walk through the
2099 // list sequentially, build up a list of containers that are in the specified screen,
2100 // as well as all items in those containers.
2101 Set<Long> itemsOnScreen = new HashSet<Long>();
2102 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2103 @Override
2104 public int compare(ItemInfo lhs, ItemInfo rhs) {
2105 return (int) (lhs.container - rhs.container);
2106 }
2107 });
2108 for (ItemInfo info : allWorkspaceItems) {
2109 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2110 if (info.screenId == currentScreenId) {
2111 currentScreenItems.add(info);
2112 itemsOnScreen.add(info.id);
2113 } else {
2114 otherScreenItems.add(info);
2115 }
2116 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2117 currentScreenItems.add(info);
2118 itemsOnScreen.add(info.id);
2119 } else {
2120 if (itemsOnScreen.contains(info.container)) {
2121 currentScreenItems.add(info);
2122 itemsOnScreen.add(info.id);
2123 } else {
2124 otherScreenItems.add(info);
2125 }
2126 }
2127 }
2128 }
2129
2130 /** Filters the set of widgets which are on the specified screen. */
2131 private void filterCurrentAppWidgets(long currentScreenId,
2132 ArrayList<LauncherAppWidgetInfo> appWidgets,
2133 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2134 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2135
2136 for (LauncherAppWidgetInfo widget : appWidgets) {
2137 if (widget == null) continue;
2138 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2139 widget.screenId == currentScreenId) {
2140 currentScreenWidgets.add(widget);
2141 } else {
2142 otherScreenWidgets.add(widget);
2143 }
2144 }
2145 }
2146
2147 /** Filters the set of folders which are on the specified screen. */
2148 private void filterCurrentFolders(long currentScreenId,
2149 HashMap<Long, ItemInfo> itemsIdMap,
2150 HashMap<Long, FolderInfo> folders,
2151 HashMap<Long, FolderInfo> currentScreenFolders,
2152 HashMap<Long, FolderInfo> otherScreenFolders) {
2153
2154 for (long id : folders.keySet()) {
2155 ItemInfo info = itemsIdMap.get(id);
2156 FolderInfo folder = folders.get(id);
2157 if (info == null || folder == null) continue;
2158 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2159 info.screenId == currentScreenId) {
2160 currentScreenFolders.put(id, folder);
2161 } else {
2162 otherScreenFolders.put(id, folder);
2163 }
2164 }
2165 }
2166
2167 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2168 * right) */
2169 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2170 final LauncherAppState app = LauncherAppState.getInstance();
2171 final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2172 // XXX: review this
2173 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2174 @Override
2175 public int compare(ItemInfo lhs, ItemInfo rhs) {
2176 int cellCountX = (int) grid.numColumns;
2177 int cellCountY = (int) grid.numRows;
2178 int screenOffset = cellCountX * cellCountY;
2179 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
2180 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
2181 lhs.cellY * cellCountX + lhs.cellX);
2182 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
2183 rhs.cellY * cellCountX + rhs.cellX);
2184 return (int) (lr - rr);
2185 }
2186 });
2187 }
2188
2189 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2190 final ArrayList<Long> orderedScreens) {
2191 final Runnable r = new Runnable() {
2192 @Override
2193 public void run() {
2194 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2195 if (callbacks != null) {
2196 callbacks.bindScreens(orderedScreens);
2197 }
2198 }
2199 };
2200 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2201 }
2202
2203 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2204 final ArrayList<ItemInfo> workspaceItems,
2205 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2206 final HashMap<Long, FolderInfo> folders,
2207 ArrayList<Runnable> deferredBindRunnables) {
2208
2209 final boolean postOnMainThread = (deferredBindRunnables != null);
2210
2211 // Bind the workspace items
2212 int N = workspaceItems.size();
2213 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2214 final int start = i;
2215 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2216 final Runnable r = new Runnable() {
2217 @Override
2218 public void run() {
2219 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2220 if (callbacks != null) {
2221 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2222 false);
2223 }
2224 }
2225 };
2226 if (postOnMainThread) {
2227 deferredBindRunnables.add(r);
2228 } else {
2229 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2230 }
2231 }
2232
2233 // Bind the folders
2234 if (!folders.isEmpty()) {
2235 final Runnable r = new Runnable() {
2236 public void run() {
2237 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2238 if (callbacks != null) {
2239 callbacks.bindFolders(folders);
2240 }
2241 }
2242 };
2243 if (postOnMainThread) {
2244 deferredBindRunnables.add(r);
2245 } else {
2246 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2247 }
2248 }
2249
2250 // Bind the widgets, one at a time
2251 N = appWidgets.size();
2252 for (int i = 0; i < N; i++) {
2253 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2254 final Runnable r = new Runnable() {
2255 public void run() {
2256 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2257 if (callbacks != null) {
2258 callbacks.bindAppWidget(widget);
2259 }
2260 }
2261 };
2262 if (postOnMainThread) {
2263 deferredBindRunnables.add(r);
2264 } else {
2265 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2266 }
2267 }
2268 }
2269
2270 /**
2271 * Binds all loaded data to actual views on the main thread.
2272 */
2273 private void bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath) {
2274 final long t = SystemClock.uptimeMillis();
2275 Runnable r;
2276
2277 // Don't use these two variables in any of the callback runnables.
2278 // Otherwise we hold a reference to them.
2279 final Callbacks oldCallbacks = mCallbacks.get();
2280 if (oldCallbacks == null) {
2281 // This launcher has exited and nobody bothered to tell us. Just bail.
2282 Log.w(TAG, "LoaderTask running with no launcher");
2283 return;
2284 }
2285
2286 // Save a copy of all the bg-thread collections
2287 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2288 ArrayList<LauncherAppWidgetInfo> appWidgets =
2289 new ArrayList<LauncherAppWidgetInfo>();
2290 HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
2291 HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
2292 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2293 synchronized (sBgLock) {
2294 workspaceItems.addAll(sBgWorkspaceItems);
2295 appWidgets.addAll(sBgAppWidgets);
2296 folders.putAll(sBgFolders);
2297 itemsIdMap.putAll(sBgItemsIdMap);
2298 orderedScreenIds.addAll(sBgWorkspaceScreens);
2299 }
2300
2301 final boolean isLoadingSynchronously =
2302 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2303 int currScreen = isLoadingSynchronously ? synchronizeBindPage :
2304 oldCallbacks.getCurrentWorkspaceScreen();
2305 if (currScreen >= orderedScreenIds.size()) {
2306 // There may be no workspace screens (just hotseat items and an empty page).
2307 currScreen = PagedView.INVALID_RESTORE_PAGE;
2308 }
2309 final int currentScreen = currScreen;
2310 final long currentScreenId = currentScreen < 0
2311 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
2312
2313 // Load all the items that are on the current page first (and in the process, unbind
2314 // all the existing workspace items before we call startBinding() below.
2315 unbindWorkspaceItemsOnMainThread();
2316
2317 // Separate the items that are on the current screen, and all the other remaining items
2318 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2319 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2320 ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
2321 new ArrayList<LauncherAppWidgetInfo>();
2322 ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
2323 new ArrayList<LauncherAppWidgetInfo>();
2324 HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
2325 HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
2326
2327 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2328 otherWorkspaceItems);
2329 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2330 otherAppWidgets);
2331 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
2332 otherFolders);
2333 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2334 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2335
2336 // Tell the workspace that we're about to start binding items
2337 r = new Runnable() {
2338 public void run() {
2339 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2340 if (callbacks != null) {
2341 callbacks.startBinding();
2342 }
2343 }
2344 };
2345 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2346
2347 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2348
2349 // Load items on the current page
2350 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
2351 currentFolders, null);
2352 if (isLoadingSynchronously) {
2353 r = new Runnable() {
2354 public void run() {
2355 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2356 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2357 callbacks.onPageBoundSynchronously(currentScreen);
2358 }
2359 }
2360 };
2361 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2362 }
2363
2364 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2365 // work until after the first render)
2366 mDeferredBindRunnables.clear();
2367 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
2368 (isLoadingSynchronously ? mDeferredBindRunnables : null));
2369
2370 // Tell the workspace that we're done binding items
2371 r = new Runnable() {
2372 public void run() {
2373 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2374 if (callbacks != null) {
2375 callbacks.finishBindingItems(isUpgradePath);
2376 }
2377
2378 // If we're profiling, ensure this is the last thing in the queue.
2379 if (DEBUG_LOADERS) {
2380 Log.d(TAG, "bound workspace in "
2381 + (SystemClock.uptimeMillis()-t) + "ms");
2382 }
2383
2384 mIsLoadingAndBindingWorkspace = false;
2385 }
2386 };
2387 if (isLoadingSynchronously) {
2388 mDeferredBindRunnables.add(r);
2389 } else {
2390 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
2391 }
2392 }
2393
2394 private void loadAndBindAllApps() {
2395 if (DEBUG_LOADERS) {
2396 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2397 }
2398 if (!mAllAppsLoaded) {
2399 loadAllApps();
2400 synchronized (LoaderTask.this) {
2401 if (mStopped) {
2402 return;
2403 }
2404 mAllAppsLoaded = true;
2405 }
2406 } else {
2407 onlyBindAllApps();
2408 }
2409 }
2410
2411 private void onlyBindAllApps() {
2412 final Callbacks oldCallbacks = mCallbacks.get();
2413 if (oldCallbacks == null) {
2414 // This launcher has exited and nobody bothered to tell us. Just bail.
2415 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2416 return;
2417 }
2418
2419 // shallow copy
2420 @SuppressWarnings("unchecked")
2421 final ArrayList<AppInfo> list
2422 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2423 Runnable r = new Runnable() {
2424 public void run() {
2425 final long t = SystemClock.uptimeMillis();
2426 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2427 if (callbacks != null) {
2428 callbacks.bindAllApplications(list);
2429 }
2430 if (DEBUG_LOADERS) {
2431 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2432 + (SystemClock.uptimeMillis()-t) + "ms");
2433 }
2434 }
2435 };
2436 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2437 if (isRunningOnMainThread) {
2438 r.run();
2439 } else {
2440 mHandler.post(r);
2441 }
2442 }
2443
2444 private void loadAllApps() {
2445 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2446
2447 final Callbacks oldCallbacks = mCallbacks.get();
2448 if (oldCallbacks == null) {
2449 // This launcher has exited and nobody bothered to tell us. Just bail.
2450 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2451 return;
2452 }
2453
2454 final PackageManager packageManager = mContext.getPackageManager();
2455 final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
2456 mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2457
2458 // Clear the list of apps
2459 mBgAllAppsList.clear();
2460
2461 // Query for the set of apps
2462 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2463 List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
2464 if (DEBUG_LOADERS) {
2465 Log.d(TAG, "queryIntentActivities took "
2466 + (SystemClock.uptimeMillis()-qiaTime) + "ms");
2467 Log.d(TAG, "queryIntentActivities got " + apps.size() + " apps");
2468 }
2469 // Fail if we don't have any apps
2470 if (apps == null || apps.isEmpty()) {
2471 return;
2472 }
2473 // Sort the applications by name
2474 final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2475 Collections.sort(apps,
2476 new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
2477 if (DEBUG_LOADERS) {
2478 Log.d(TAG, "sort took "
2479 + (SystemClock.uptimeMillis()-sortTime) + "ms");
2480 }
2481
2482 // Create the ApplicationInfos
2483 for (int i = 0; i < apps.size(); i++) {
2484 ResolveInfo app = apps.get(i);
2485 // This builds the icon bitmaps.
2486 mBgAllAppsList.add(new AppInfo(packageManager, app,
2487 mIconCache, mLabelCache));
2488 }
2489
2490 // Huh? Shouldn't this be inside the Runnable below?
2491 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2492 mBgAllAppsList.added = new ArrayList<AppInfo>();
2493
2494 // Post callback on main thread
2495 mHandler.post(new Runnable() {
2496 public void run() {
2497 final long bindTime = SystemClock.uptimeMillis();
2498 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2499 if (callbacks != null) {
2500 callbacks.bindAllApplications(added);
2501 if (DEBUG_LOADERS) {
2502 Log.d(TAG, "bound " + added.size() + " apps in "
2503 + (SystemClock.uptimeMillis() - bindTime) + "ms");
2504 }
2505 } else {
2506 Log.i(TAG, "not binding apps: no Launcher activity");
2507 }
2508 }
2509 });
2510
2511 if (DEBUG_LOADERS) {
2512 Log.d(TAG, "Icons processed in "
2513 + (SystemClock.uptimeMillis() - loadTime) + "ms");
2514 }
2515 }
2516
2517 public void dumpState() {
2518 synchronized (sBgLock) {
2519 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2520 Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
2521 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2522 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2523 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2524 }
2525 }
2526 }
2527
2528 void enqueuePackageUpdated(PackageUpdatedTask task) {
2529 sWorker.post(task);
2530 }
2531
2532 private class PackageUpdatedTask implements Runnable {
2533 int mOp;
2534
2535 String[] mPackages;
2536
2537 public static final int OP_NONE = 0;
2538
2539 public static final int OP_ADD = 1;
2540
2541 public static final int OP_UPDATE = 2;
2542
2543 // uninstlled
2544 public static final int OP_REMOVE = 3; // uninstlled
2545
2546 // external media unmounted
2547 public static final int OP_UNAVAILABLE = 4; // external media unmounted
2548
2549 public PackageUpdatedTask(int op, String[] packages) {
2550 mOp = op;
2551 mPackages = packages;
2552 }
2553
2554 public void run() {
2555 final Context context = mApp.getContext();
2556 final String[] packages = mPackages;
2557 final int N = packages.length;
2558 switch (mOp) {
2559 case OP_ADD :
2560 for (int i = 0; i < N; i++) {
2561 if (DEBUG_LOADERS) {
2562 Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
2563 }
2564 mBgAllAppsList.addPackage(context, packages[i]);
2565 }
2566 break;
2567 case OP_UPDATE :
2568 for (int i = 0; i < N; i++) {
2569 if (DEBUG_LOADERS) {
2570 Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
2571 }
2572 mBgAllAppsList.updatePackage(context, packages[i]);
2573 WidgetPreviewLoader.removePackageFromDb(mApp.getWidgetPreviewCacheDb(), packages[🔵
2574 }
2575 break;
2576 case OP_REMOVE :
2577 case OP_UNAVAILABLE :
2578 for (int i = 0; i < N; i++) {
2579 if (DEBUG_LOADERS) {
2580 Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
2581 }
2582 mBgAllAppsList.removePackage(packages[i]);
2583 WidgetPreviewLoader.removePackageFromDb(mApp.getWidgetPreviewCacheDb(), packages[🔵
2584 }
2585 break;
2586 }
2587 ArrayList<AppInfo> added = null;
2588 ArrayList<AppInfo> modified = null;
2589 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
2590 if (mBgAllAppsList.added.size() > 0) {
2591 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
2592 mBgAllAppsList.added.clear();
2593 }
2594 if (mBgAllAppsList.modified.size() > 0) {
2595 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
2596 mBgAllAppsList.modified.clear();
2597 }
2598 if (mBgAllAppsList.removed.size() > 0) {
2599 removedApps.addAll(mBgAllAppsList.removed);
2600 mBgAllAppsList.removed.clear();
2601 }
2602 final Callbacks callbacks = (mCallbacks != null) ? mCallbacks.get() : null;
2603 if (callbacks == null) {
2604 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
2605 return;
2606 }
2607 if (added != null) {
2608 // Ensure that we add all the workspace applications to the db
2609 Callbacks cb = (mCallbacks != null) ? mCallbacks.get() : null;
2610 if (!LauncherAppState.isDisableAllApps()) {
2611 addAndBindAddedApps(context, new ArrayList<ItemInfo>(), cb, added);
2612 } else {
2613 final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added);
2614 addAndBindAddedApps(context, addedInfos, cb, added);
2615 }
2616 }
2617 if (modified != null) {
2618 final ArrayList<AppInfo> modifiedFinal = modified;
2619 // Update the launcher db to reflect the changes
2620 for (AppInfo a : modifiedFinal) {
2621 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName);
2622 for (ItemInfo i : infos) {
2623 if (isShortcutInfoUpdateable(i)) {
2624 ShortcutInfo info = ((ShortcutInfo) (i));
2625 info.title = a.title.toString();
2626 updateItemInDatabase(context, info);
2627 }
2628 }
2629 }
2630 mHandler.post(new Runnable() {
2631 public void run() {
2632 Callbacks cb = (mCallbacks != null) ? mCallbacks.get() : null;
2633 if ((callbacks == cb) && (cb != null)) {
2634 callbacks.bindAppsUpdated(modifiedFinal);
2635 }
2636 }
2637 });
2638 }
2639 final ArrayList<String> removedPackageNames = new ArrayList<String>();
2640 if (mOp == OP_REMOVE) {
2641 // Mark all packages in the broadcast to be removed
2642 removedPackageNames.addAll(Arrays.asList(packages));
2643 } else if (mOp == OP_UPDATE) {
2644 // Mark disabled packages in the broadcast to be removed
2645 final PackageManager pm = context.getPackageManager();
2646 for (int i = 0; i < N; i++) {
2647 if (isPackageDisabled(pm, packages[i])) {
2648 removedPackageNames.add(packages[i]);
2649 }
2650 }
2651 }
2652 // Remove all the components associated with this package
2653 for (String pn : removedPackageNames) {
2654 ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn);
2655 for (ItemInfo i : infos) {
2656 deleteItemFromDatabase(context, i);
2657 }
2658 }
2659 // Remove all the specific components
2660 for (AppInfo a : removedApps) {
2661 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName);
2662 for (ItemInfo i : infos) {
2663 deleteItemFromDatabase(context, i);
2664 }
2665 }
2666 if ((!removedPackageNames.isEmpty()) || (!removedApps.isEmpty())) {
2667 // Remove any queued items from the install queue
2668 String spKey = LauncherAppState.getSharedPreferencesKey();
2669 SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE);
2670 InstallShortcutReceiver.removeFromInstallQueue(sp, removedPackageNames);
2671 // Call the components-removed callback
2672 mHandler.post(new Runnable() {
2673 public void run() {
2674 Callbacks cb = (mCallbacks != null) ? mCallbacks.get() : null;
2675 if ((callbacks == cb) && (cb != null)) {
2676 callbacks.bindComponentsRemoved(removedPackageNames, removedApps);
2677 }
2678 }
2679 });
2680 }
2681 final ArrayList<Object> widgetsAndShortcuts = getSortedWidgetsAndShortcuts(context);
2682 mHandler.post(new Runnable() {
2683 @Override
2684 public void run() {
2685 Callbacks cb = (mCallbacks != null) ? mCallbacks.get() : null;
2686 if ((callbacks == cb) && (cb != null)) {
2687 callbacks.bindPackagesUpdated(widgetsAndShortcuts);
2688 }
2689 }
2690 });
2691 // Write all the logs to disk
2692 mHandler.post(new Runnable() {
2693 public void run() {
2694 Callbacks cb = (mCallbacks != null) ? mCallbacks.get() : null;
2695 if ((callbacks == cb) && (cb != null)) {
2696 callbacks.dumpLogsToLocalData();
2697 }
2698 }
2699 });
2700 }
2701 }
2702
2703 // Returns a list of ResolveInfos/AppWindowInfos in sorted order
2704 public static ArrayList<Object> getSortedWidgetsAndShortcuts(Context context) {
2705 PackageManager packageManager = context.getPackageManager();
2706 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
2707 widgetsAndShortcuts.addAll(AppWidgetManager.getInstance(context).getInstalledProviders());
2708 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2709 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
2710 Collections.sort(widgetsAndShortcuts,
2711 new LauncherModel.WidgetAndShortcutNameComparator(packageManager));
2712 return widgetsAndShortcuts;
2713 }
2714
2715 private static boolean isPackageDisabled(PackageManager pm, String packageName) {
2716 try {
2717 PackageInfo pi = pm.getPackageInfo(packageName, 0);
2718 return !pi.applicationInfo.enabled;
2719 } catch (NameNotFoundException e) {
2720 // Fall through
2721 }
2722 return false;
2723 }
2724
2725 public static boolean isValidPackageComponent(PackageManager pm, ComponentName cn) {
2726 if (cn == null) {
2727 return false;
2728 }
2729 if (isPackageDisabled(pm, cn.getPackageName())) {
2730 return false;
2731 }
2732
2733 try {
2734 // Check the activity
2735 PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0);
2736 return (pm.getActivityInfo(cn, 0) != null);
2737 } catch (NameNotFoundException e) {
2738 return false;
2739 }
2740 }
2741
2742 /**
2743 * Make an ShortcutInfo object for a restored application or shortcut item that points
2744 * to a package that is not yet installed on the system.
2745 */
2746 public ShortcutInfo getRestoredItemInfo(Cursor cursor, int titleIndex, Intent intent) {
2747 final ShortcutInfo info = new ShortcutInfo();
2748 info.usingFallbackIcon = true;
2749 info.setIcon(getFallbackIcon());
2750 if (cursor != null) {
2751 info.title = cursor.getString(titleIndex);
2752 } else {
2753 info.title = "";
2754 }
2755 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
2756 info.restoredIntent = intent;
2757 return info;
2758 }
2759
2760 /**
2761 * Make an Intent object for a restored application or shortcut item that points
2762 * to the market page for the item.
2763 */
2764 private Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
2765 final boolean debug = false;
2766 ComponentName componentName = intent.getComponent();
2767 Intent marketIntent = new Intent(Intent.ACTION_VIEW);
2768 Uri marketUri = new Uri.Builder().scheme("market").authority("details").appendQueryParameter("id"🔵
2769 if (debug) {
2770 Log.d(TAG, "manufactured intent uri: " + marketUri.toString());
2771 }
2772 marketIntent.setData(marketUri);
2773 return marketIntent;
2774 }
2775
2776 /**
2777 * This is called from the code that adds shortcuts from the intent receiver. This
2778 * doesn't have a Cursor, but
2779 */
2780 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) {
2781 return getShortcutInfo(manager, intent, context, null, -1, -1, null);
2782 }
2783
2784 /**
2785 * Make an ShortcutInfo object for a shortcut that is an application.
2786 *
2787 * If c is not null, then it will be used to fill in missing data like the title and icon.
2788 */
2789 public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context,
2790 Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) {
2791 ComponentName componentName = intent.getComponent();
2792 final ShortcutInfo info = new ShortcutInfo();
2793 if (componentName != null && !isValidPackageComponent(manager, componentName)) {
2794 Log.d(TAG, "Invalid package found in getShortcutInfo: " + componentName);
2795 return null;
2796 } else {
2797 try {
2798 PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0);
2799 info.initFlagsAndFirstInstallTime(pi);
2800 } catch (NameNotFoundException e) {
2801 Log.d(TAG, "getPackInfo failed for package " +
2802 componentName.getPackageName());
2803 }
2804 }
2805
2806 // TODO: See if the PackageManager knows about this case. If it doesn't
2807 // then return null & delete this.
2808
2809 // the resource -- This may implicitly give us back the fallback icon,
2810 // but don't worry about that. All we're doing with usingFallbackIcon is
2811 // to avoid saving lots of copies of that in the database, and most apps
2812 // have icons anyway.
2813
2814 // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and
2815 // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info
2816 // via resolveActivity().
2817 Bitmap icon = null;
2818 ResolveInfo resolveInfo = null;
2819 ComponentName oldComponent = intent.getComponent();
2820 Intent newIntent = new Intent(intent.getAction(), null);
2821 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
2822 newIntent.setPackage(oldComponent.getPackageName());
2823 List<ResolveInfo> infos = manager.queryIntentActivities(newIntent, 0);
2824 for (ResolveInfo i : infos) {
2825 ComponentName cn = new ComponentName(i.activityInfo.packageName,
2826 i.activityInfo.name);
2827 if (cn.equals(oldComponent)) {
2828 resolveInfo = i;
2829 }
2830 }
2831 if (resolveInfo == null) {
2832 resolveInfo = manager.resolveActivity(intent, 0);
2833 }
2834 if (resolveInfo != null) {
2835 icon = mIconCache.getIcon(componentName, resolveInfo, labelCache);
2836 }
2837 // the db
2838 if (icon == null) {
2839 if (c != null) {
2840 icon = getIconFromCursor(c, iconIndex, context);
2841 }
2842 }
2843 // the fallback icon
2844 if (icon == null) {
2845 icon = getFallbackIcon();
2846 info.usingFallbackIcon = true;
2847 }
2848 info.setIcon(icon);
2849
2850 // from the resource
2851 if (resolveInfo != null) {
2852 ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo);
2853 if (labelCache != null && labelCache.containsKey(key)) {
2854 info.title = labelCache.get(key);
2855 } else {
2856 info.title = resolveInfo.activityInfo.loadLabel(manager);
2857 if (labelCache != null) {
2858 labelCache.put(key, info.title);
2859 }
2860 }
2861 }
2862 // from the db
2863 if (info.title == null) {
2864 if (c != null) {
2865 info.title = c.getString(titleIndex);
2866 }
2867 }
2868 // fall back to the class name of the activity
2869 if (info.title == null) {
2870 info.title = componentName.getClassName();
2871 }
2872 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
2873 return info;
2874 }
2875
2876 static ArrayList<ItemInfo> filterItemInfos(Collection<ItemInfo> infos,
2877 ItemInfoFilter f) {
2878 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
2879 for (ItemInfo i : infos) {
2880 if (i instanceof ShortcutInfo) {
2881 ShortcutInfo info = (ShortcutInfo) i;
2882 ComponentName cn = info.intent.getComponent();
2883 if (cn != null && f.filterItem(null, info, cn)) {
2884 filtered.add(info);
2885 }
2886 } else if (i instanceof FolderInfo) {
2887 FolderInfo info = (FolderInfo) i;
2888 for (ShortcutInfo s : info.contents) {
2889 ComponentName cn = s.intent.getComponent();
2890 if (cn != null && f.filterItem(info, s, cn)) {
2891 filtered.add(s);
2892 }
2893 }
2894 } else if (i instanceof LauncherAppWidgetInfo) {
2895 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
2896 ComponentName cn = info.providerName;
2897 if (cn != null && f.filterItem(null, info, cn)) {
2898 filtered.add(info);
2899 }
2900 }
2901 }
2902 return new ArrayList<ItemInfo>(filtered);
2903 }
2904
2905 private ArrayList<ItemInfo> getItemInfoForPackageName(final String pn) {
2906 ItemInfoFilter filter = new ItemInfoFilter() {
2907 @Override
2908 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
2909 return cn.getPackageName().equals(pn);
2910 }
2911 };
2912 return filterItemInfos(sBgItemsIdMap.values(), filter);
2913 }
2914
2915 private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname) {
2916 ItemInfoFilter filter = new ItemInfoFilter() {
2917 @Override
2918 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
2919 return cn.equals(cname);
2920 }
2921 };
2922 return filterItemInfos(sBgItemsIdMap.values(), filter);
2923 }
2924
2925 public static boolean isShortcutInfoUpdateable(ItemInfo i) {
2926 if (i instanceof ShortcutInfo) {
2927 ShortcutInfo info = ((ShortcutInfo) (i));
2928 // We need to check for ACTION_MAIN otherwise getComponent() might
2929 // return null for some shortcuts (for instance, for shortcuts to
2930 // web pages.)
2931 Intent intent = info.intent;
2932 ComponentName name = intent.getComponent();
2933 if (((info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) && Intent.ACTION_MAI🔵
2934 return true;
2935 }
2936 // placeholder shortcuts get special treatment, let them through too.
2937 if (info.getRestoredIntent() != null) {
2938 return true;
2939 }
2940 }
2941 return false;
2942 }
2943
2944 /**
2945 * Make an ShortcutInfo object for a shortcut that isn't an application.
2946 */
2947 private ShortcutInfo getShortcutInfo(Cursor c, Context context,
2948 int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex,
2949 int titleIndex) {
2950
2951 Bitmap icon = null;
2952 final ShortcutInfo info = new ShortcutInfo();
2953 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
2954
2955 // TODO: If there's an explicit component and we can't install that, delete it.
2956
2957 info.title = c.getString(titleIndex);
2958
2959 int iconType = c.getInt(iconTypeIndex);
2960 switch (iconType) {
2961 case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
2962 String packageName = c.getString(iconPackageIndex);
2963 String resourceName = c.getString(iconResourceIndex);
2964 PackageManager packageManager = context.getPackageManager();
2965 info.customIcon = false;
2966 // the resource
2967 try {
2968 Resources resources = packageManager.getResourcesForApplication(packageName);
2969 if (resources != null) {
2970 final int id = resources.getIdentifier(resourceName, null, null);
2971 icon = Utilities.createIconBitmap(
2972 mIconCache.getFullResIcon(resources, id), context);
2973 }
2974 } catch (Exception e) {
2975 // drop this. we have other places to look for icons
2976 }
2977 // the db
2978 if (icon == null) {
2979 icon = getIconFromCursor(c, iconIndex, context);
2980 }
2981 // the fallback icon
2982 if (icon == null) {
2983 icon = getFallbackIcon();
2984 info.usingFallbackIcon = true;
2985 }
2986 break;
2987 case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
2988 icon = getIconFromCursor(c, iconIndex, context);
2989 if (icon == null) {
2990 icon = getFallbackIcon();
2991 info.customIcon = false;
2992 info.usingFallbackIcon = true;
2993 } else {
2994 info.customIcon = true;
2995 }
2996 break;
2997 default:
2998 icon = getFallbackIcon();
2999 info.usingFallbackIcon = true;
3000 info.customIcon = false;
3001 break;
3002 }
3003 info.setIcon(icon);
3004 return info;
3005 }
3006
3007 Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) {
3008 @SuppressWarnings("all") // suppress dead code warning
3009 final boolean debug = false;
3010 if (debug) {
3011 Log.d(TAG, "getIconFromCursor app="
3012 + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE)));
3013 }
3014 byte[] data = c.getBlob(iconIndex);
3015 try {
3016 return Utilities.createIconBitmap(
3017 BitmapFactory.decodeByteArray(data, 0, data.length), context);
3018 } catch (Exception e) {
3019 return null;
3020 }
3021 }
3022
3023 ShortcutInfo addShortcut(Context context, Intent data, long container, int screen,
3024 int cellX, int cellY, boolean notify) {
3025 final ShortcutInfo info = infoFromShortcutIntent(context, data, null);
3026 if (info == null) {
3027 return null;
3028 }
3029 addItemToDatabase(context, info, container, screen, cellX, cellY, notify);
3030
3031 return info;
3032 }
3033
3034 /**
3035 * Attempts to find an AppWidgetProviderInfo that matches the given component.
3036 */
3037 AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,
3038 ComponentName component) {
3039 List<AppWidgetProviderInfo> widgets =
3040 AppWidgetManager.getInstance(context).getInstalledProviders();
3041 for (AppWidgetProviderInfo info : widgets) {
3042 if (info.provider.equals(component)) {
3043 return info;
3044 }
3045 }
3046 return null;
3047 }
3048
3049 /**
3050 * Returns a list of all the widgets that can handle configuration with a particular mimeType.
3051 */
3052 List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) {
3053 final PackageManager packageManager = context.getPackageManager();
3054 final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities =
3055 new ArrayList<WidgetMimeTypeHandlerData>();
3056
3057 final Intent supportsIntent =
3058 new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE);
3059 supportsIntent.setType(mimeType);
3060
3061 // Create a set of widget configuration components that we can test against
3062 final List<AppWidgetProviderInfo> widgets =
3063 AppWidgetManager.getInstance(context).getInstalledProviders();
3064 final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget =
3065 new HashMap<ComponentName, AppWidgetProviderInfo>();
3066 for (AppWidgetProviderInfo info : widgets) {
3067 configurationComponentToWidget.put(info.configure, info);
3068 }
3069
3070 // Run through each of the intents that can handle this type of clip data, and cross
3071 // reference them with the components that are actual configuration components
3072 final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent,
3073 PackageManager.MATCH_DEFAULT_ONLY);
3074 for (ResolveInfo info : activities) {
3075 final ActivityInfo activityInfo = info.activityInfo;
3076 final ComponentName infoComponent = new ComponentName(activityInfo.packageName,
3077 activityInfo.name);
3078 if (configurationComponentToWidget.containsKey(infoComponent)) {
3079 supportedConfigurationActivities.add(
3080 new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info,
3081 configurationComponentToWidget.get(infoComponent)));
3082 }
3083 }
3084 return supportedConfigurationActivities;
3085 }
3086
3087 ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) {
3088 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3089 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3090 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3091
3092 if (intent == null) {
3093 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3094 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3095 return null;
3096 }
3097
3098 Bitmap icon = null;
3099 boolean customIcon = false;
3100 ShortcutIconResource iconResource = null;
3101
3102 if (bitmap != null && bitmap instanceof Bitmap) {
3103 icon = Utilities.createIconBitmap(new FastBitmapDrawable((Bitmap)bitmap), context);
3104 customIcon = true;
3105 } else {
3106 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3107 if (extra != null && extra instanceof ShortcutIconResource) {
3108 try {
3109 iconResource = (ShortcutIconResource) extra;
3110 final PackageManager packageManager = context.getPackageManager();
3111 Resources resources = packageManager.getResourcesForApplication(
3112 iconResource.packageName);
3113 final int id = resources.getIdentifier(iconResource.resourceName, null, null);
3114 icon = Utilities.createIconBitmap(
3115 mIconCache.getFullResIcon(resources, id), context);
3116 } catch (Exception e) {
3117 Log.w(TAG, "Could not load shortcut icon: " + extra);
3118 }
3119 }
3120 }
3121
3122 final ShortcutInfo info = new ShortcutInfo();
3123
3124 if (icon == null) {
3125 if (fallbackIcon != null) {
3126 icon = fallbackIcon;
3127 } else {
3128 icon = getFallbackIcon();
3129 info.usingFallbackIcon = true;
3130 }
3131 }
3132 info.setIcon(icon);
3133
3134 info.title = name;
3135 info.intent = intent;
3136 info.customIcon = customIcon;
3137 info.iconResource = iconResource;
3138
3139 return info;
3140 }
3141
3142 boolean queueIconToBeChecked(HashMap<Object, byte[]> cache, ShortcutInfo info, Cursor c,
3143 int iconIndex) {
3144 // If apps can't be on SD, don't even bother.
3145 if (!mAppsCanBeOnRemoveableStorage) {
3146 return false;
3147 }
3148 // If this icon doesn't have a custom icon, check to see
3149 // what's stored in the DB, and if it doesn't match what
3150 // we're going to show, store what we are going to show back
3151 // into the DB. We do this so when we're loading, if the
3152 // package manager can't find an icon (for example because
3153 // the app is on SD) then we can use that instead.
3154 if (!info.customIcon && !info.usingFallbackIcon) {
3155 cache.put(info, c.getBlob(iconIndex));
3156 return true;
3157 }
3158 return false;
3159 }
3160
3161 void updateSavedIcon(Context context, ShortcutInfo info, byte[] data) {
3162 boolean needSave = false;
3163 try {
3164 if (data != null) {
3165 Bitmap saved = BitmapFactory.decodeByteArray(data, 0, data.length);
3166 Bitmap loaded = info.getIcon(mIconCache);
3167 needSave = !saved.sameAs(loaded);
3168 } else {
3169 needSave = true;
3170 }
3171 } catch (Exception e) {
3172 needSave = true;
3173 }
3174 if (needSave) {
3175 Log.d(TAG, "going to save icon bitmap for info=" + info);
3176 // This is slower than is ideal, but this only happens once
3177 // or when the app is updated with a new icon.
3178 updateItemInDatabase(context, info);
3179 }
3180 }
3181
3182 /**
3183 * Return an existing FolderInfo object if we have encountered this ID previously,
3184 * or make a new one.
3185 */
3186 private static FolderInfo findOrMakeFolder(HashMap<Long, FolderInfo> folders, long id) {
3187 // See if a placeholder was created for us already
3188 FolderInfo folderInfo = folders.get(id);
3189 if (folderInfo == null) {
3190 // No placeholder -- create a new instance
3191 folderInfo = new FolderInfo();
3192 folders.put(id, folderInfo);
3193 }
3194 return folderInfo;
3195 }
3196
3197 public static final Comparator<AppInfo> getAppNameComparator() {
3198 final Collator collator = Collator.getInstance();
3199 return new Comparator<AppInfo>() {
3200 public final int compare(AppInfo a, AppInfo b) {
3201 int result = collator.compare(a.title.toString().trim(),
3202 b.title.toString().trim());
3203 if (result == 0) {
3204 result = a.componentName.compareTo(b.componentName);
3205 }
3206 return result;
3207 }
3208 };
3209 }
3210
3211 public static final Comparator<AppInfo> APP_INSTALL_TIME_COMPARATOR = new Comparator<AppInfo>() {
3212 public final int compare(AppInfo a, AppInfo b) {
3213 if (a.firstInstallTime < b.firstInstallTime) {
3214 return 1;
3215 }
3216 if (a.firstInstallTime > b.firstInstallTime) {
3217 return -1;
3218 }
3219 return 0;
3220 }
3221 };
3222
3223 public static final Comparator<AppWidgetProviderInfo> getWidgetNameComparator() {
3224 final Collator collator = Collator.getInstance();
3225 return new Comparator<AppWidgetProviderInfo>() {
3226 public final int compare(AppWidgetProviderInfo a, AppWidgetProviderInfo b) {
3227 return collator.compare(a.label.toString().trim(), b.label.toString().trim());
3228 }
3229 };
3230 }
3231
3232 static ComponentName getComponentNameFromResolveInfo(ResolveInfo info) {
3233 if (info.activityInfo != null) {
3234 return new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
3235 } else {
3236 return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
3237 }
3238 }
3239
3240 public static class ShortcutNameComparator implements Comparator<ResolveInfo> {
3241 private Collator mCollator;
3242
3243 private PackageManager mPackageManager;
3244
3245 private HashMap<Object, CharSequence> mLabelCache;
3246
3247 ShortcutNameComparator(PackageManager pm) {
3248 mPackageManager = pm;
3249 mLabelCache = new HashMap<Object, CharSequence>();
3250 mCollator = Collator.getInstance();
3251 }
3252
3253 ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) {
3254 mPackageManager = pm;
3255 mLabelCache = labelCache;
3256 mCollator = Collator.getInstance();
3257 }
3258
3259 public final int compare(ResolveInfo a, ResolveInfo b) {
3260 CharSequence labelA;
3261 CharSequence labelB;
3262 ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a);
3263 ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b);
3264 if (mLabelCache.containsKey(keyA)) {
3265 labelA = mLabelCache.get(keyA);
3266 } else {
3267 labelA = a.loadLabel(mPackageManager).toString().trim();
3268 mLabelCache.put(keyA, labelA);
3269 }
3270 if (mLabelCache.containsKey(keyB)) {
3271 labelB = mLabelCache.get(keyB);
3272 } else {
3273 labelB = b.loadLabel(mPackageManager).toString().trim();
3274 mLabelCache.put(keyB, labelB);
3275 }
3276 return mCollator.compare(labelA, labelB);
3277 }
3278 }
3279
3280 public static class WidgetAndShortcutNameComparator implements Comparator<Object> {
3281 private Collator mCollator;
3282
3283 private PackageManager mPackageManager;
3284
3285 private HashMap<Object, String> mLabelCache;
3286
3287 WidgetAndShortcutNameComparator(PackageManager pm) {
3288 mPackageManager = pm;
3289 mLabelCache = new HashMap<Object, String>();
3290 mCollator = Collator.getInstance();
3291 }
3292
3293 public final int compare(Object a, Object b) {
3294 String labelA;
3295 String labelB;
3296 if (mLabelCache.containsKey(a)) {
3297 labelA = mLabelCache.get(a);
3298 } else {
3299 labelA = (a instanceof AppWidgetProviderInfo) ? ((AppWidgetProviderInfo) (a)).label : ((R🔵
3300 mLabelCache.put(a, labelA);
3301 }
3302 if (mLabelCache.containsKey(b)) {
3303 labelB = mLabelCache.get(b);
3304 } else {
3305 labelB = (b instanceof AppWidgetProviderInfo) ? ((AppWidgetProviderInfo) (b)).label : ((R🔵
3306 mLabelCache.put(b, labelB);
3307 }
3308 return mCollator.compare(labelA, labelB);
3309 }
3310 }
3311
3312 public void dumpState() {
3313 Log.d(TAG, "mCallbacks=" + mCallbacks);
3314 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3315 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3316 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3317 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3318 if (mLoaderTask != null) {
3319 mLoaderTask.dumpState();
3320 } else {
3321 Log.d(TAG, "mLoaderTask=null");
3322 }
3323 }
3324 }
|